From 2d0e197c6b114266dc47c42a3294f163d1b9a112 Mon Sep 17 00:00:00 2001 From: John Henley Date: Thu, 19 Jun 2025 17:22:18 +0000 Subject: [PATCH 01/40] TASK: Remove unused columns from activeforums_Forums table --- Dnn.CommunityForums/DnnCommunityForums.dnn | 7 ++++- Dnn.CommunityForums/Entities/ForumInfo.cs | 19 ++++++------ .../sql/09.01.00.SqlDataProvider | 31 +++++++++++++++++++ 3 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 Dnn.CommunityForums/sql/09.01.00.SqlDataProvider diff --git a/Dnn.CommunityForums/DnnCommunityForums.dnn b/Dnn.CommunityForums/DnnCommunityForums.dnn index 9f04cea88..f57827613 100644 --- a/Dnn.CommunityForums/DnnCommunityForums.dnn +++ b/Dnn.CommunityForums/DnnCommunityForums.dnn @@ -424,10 +424,15 @@ 09.00.00.SqlDataProvider 09.00.00 + diff --git a/Dnn.CommunityForums/Entities/ForumInfo.cs b/Dnn.CommunityForums/Entities/ForumInfo.cs index 383f7517a..0f4f5daca 100644 --- a/Dnn.CommunityForums/Entities/ForumInfo.cs +++ b/Dnn.CommunityForums/Entities/ForumInfo.cs @@ -160,6 +160,7 @@ public string LastPostTopicUrl [IgnoreColumn] public bool LastPostIsTopic => this.LastReplyId == 0; + [IgnoreColumn] public string LastPostSubject { get @@ -222,15 +223,15 @@ internal DotNetNuke.Modules.ActiveForums.Entities.IPostInfo LoadLastPost() // return this.lastPostInfo = this.LastReplyId == 0 ? (DotNetNuke.Modules.ActiveForums.Entities.IPostInfo)new DotNetNuke.Modules.ActiveForums.Controllers.TopicController(this.ModuleId).GetById(this.LastTopicId) : new DotNetNuke.Modules.ActiveForums.Controllers.ReplyController(this.ModuleId).GetById(this.LastReplyId); } - - [ColumnName("LastPostAuthorName")] - public string LastPostUserName { get; set; } - - [ColumnName("LastPostAuthorId")] - public int LastPostUserID { get; set; } - - [ColumnName("LastPostDate")] - public DateTime? LastPostDateTime { get; set; } + + [IgnoreColumn] + public string LastPostUserName => this.LastPost != null && this.LastPost.Content != null ? this.LastPost.Content.AuthorName : string.Empty; + + [IgnoreColumn] + public int LastPostUserID => this.LastPost != null && this.LastPost.Content != null ? this.LastPost.Content.AuthorId : DotNetNuke.Common.Utilities.Null.NullInteger; + + [IgnoreColumn] + public DateTime? LastPostDateTime => this.LastPost != null && this.LastPost.Content != null ? (DateTime)this.LastPost.Content.DateUpdated : DotNetNuke.Common.Utilities.Null.NullDate; public int PermissionsId { get; set; } diff --git a/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider new file mode 100644 index 000000000..b3c4a2bb2 --- /dev/null +++ b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider @@ -0,0 +1,31 @@ +SET NOCOUNT ON +GO + +/* issue 792 begin - activeforums_Forums -- remove columns no longer used */ + +IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_{objectQualifier}activeforums_Forums_LastPostId]') AND type = 'D') +ALTER TABLE {databaseOwner}{objectQualifier}activeforums_Forums DROP CONSTRAINT DF_{objectQualifier}activeforums_Forums_LastPostId +GO +IF EXISTS(SELECT * FROM sys.columns WHERE [name] = N'LastPostId' AND [object_id] = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Forums]')) +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Forums] DROP COLUMN LastPostId +GO +IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_{objectQualifier}activeforums_Forums_LastPostAuthorId]') AND type = 'D') +ALTER TABLE {databaseOwner}{objectQualifier}activeforums_Forums DROP CONSTRAINT DF_{objectQualifier}activeforums_Forums_LastPostAuthorId +GO +IF EXISTS(SELECT * FROM sys.columns WHERE [name] = N'LastPostAuthorId' AND [object_id] = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Forums]')) +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Forums] DROP COLUMN LastPostAuthorId +GO +IF EXISTS(SELECT * FROM sys.columns WHERE [name] = N'LastPostAuthorName' AND [object_id] = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Forums]')) +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Forums] DROP COLUMN LastPostAuthorName +GO +IF EXISTS(SELECT * FROM sys.columns WHERE [name] = N'LastPostSubject' AND [object_id] = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Forums]')) +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Forums] DROP COLUMN LastPostSubject +GO +IF EXISTS(SELECT * FROM sys.columns WHERE [name] = N'LastPostDate' AND [object_id] = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Forums]')) +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Forums] DROP COLUMN LastPostDate +GO + +/* issue 792 end - activeforums_Forums -- remove columns no longer used */ + +/* --------------------- */ + From 1bef4f160fd859c2bfb7fafcfb471ddbfa66a0f6 Mon Sep 17 00:00:00 2001 From: John Henley Date: Fri, 13 Jun 2025 14:14:35 +0000 Subject: [PATCH 02/40] ENH: Add avatar refresh feature - Updated SQL script to modify the database schema for avatar refresh. - Added `LastAvatarRefresh` property in `ForumUserInfo` to track avatar refresh times. - Updated `DnnCommunityForums.dnn` with a new SQL data provider script for version 09.01.00. - Implemented dropdown controls for avatar refresh type in `ForumSettings.ascx`. - Introduced new localization entries for avatar refresh settings in multiple languages. - Defined new setting keys for avatar refresh in `Globals.cs`. - Developed avatar refresh logic in `AvatarRefreshQueue` and created `GravatarAvatarProvider` for fetching avatars. - Added `IAvatarProvider` interface for avatar provider contract. - Added unit tests for `GetMd5Hash` in `UtilitiesTests.cs`. --- .../ControlPanel.ascx.fr-FR.resx | 6 - .../ControlPanel.ascx.it-IT.resx | 6 - .../ControlPanel.ascx.nl-NL.resx | 6 - .../App_LocalResources/ControlPanel.ascx.resx | 6 - .../Controlpanel.ascx.de-DE.resx | 6 - .../Controlpanel.ascx.es-ES.resx | 6 - .../ForumSettings.ascx.de-DE.resx | 6 + .../ForumSettings.ascx.es-ES.resx | 6 + .../ForumSettings.ascx.fr-FR.resx | 6 + .../ForumSettings.ascx.it-IT.resx | 12 ++ .../ForumSettings.ascx.nl-NL.resx | 12 ++ .../ForumSettings.ascx.resx | 12 ++ .../SharedResources.de-DE.resx | 3 + .../SharedResources.es-ES.resx | 3 + .../SharedResources.fr-FR.resx | 3 + .../SharedResources.it-IT.resx | 3 + .../SharedResources.nl-NL.resx | 3 + .../App_LocalResources/SharedResources.resx | 3 + Dnn.CommunityForums/Classic.ascx.cs | 2 + Dnn.CommunityForums/DnnCommunityForums.dnn | 7 +- Dnn.CommunityForums/Entities/ForumUserInfo.cs | 10 + Dnn.CommunityForums/ForumSettings.ascx | 7 + Dnn.CommunityForums/ForumSettings.ascx.cs | 3 +- .../ForumSettings.ascx.designer.cs | 8 + .../Services/Avatars/AvatarRefreshQueue.cs | 190 ++++++++++++++++++ .../Avatars/GravatarAvatarProvider.cs | 103 ++++++++++ .../Services/Avatars/IAvatarProvider.cs | 35 ++++ Dnn.CommunityForums/class/Globals.cs | 7 + Dnn.CommunityForums/class/Settings.cs | 10 + Dnn.CommunityForums/class/Utilities.cs | 17 ++ .../components/Common/ForumSettingsBase.cs | 15 +- .../Helpers/UpgradeModuleSettings.cs | 36 ++++ .../components/Topics/TopicsController.cs | 14 ++ .../config/defaultsetup.config | 5 +- .../sql/09.01.00.SqlDataProvider | 128 ++++++++++++ .../sql/Uninstall.SqlDataProvider | 11 + Dnn.CommunityForumsTests/TestBase.cs | 3 +- .../class/UtilitiesTests.cs | 14 ++ DnnCommunityForums.sln | 1 + 39 files changed, 690 insertions(+), 44 deletions(-) create mode 100644 Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs create mode 100644 Dnn.CommunityForums/Services/Avatars/GravatarAvatarProvider.cs create mode 100644 Dnn.CommunityForums/Services/Avatars/IAvatarProvider.cs create mode 100644 Dnn.CommunityForums/sql/09.01.00.SqlDataProvider diff --git a/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.fr-FR.resx b/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.fr-FR.resx index 122f13200..b6713f6d6 100644 --- a/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.fr-FR.resx +++ b/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.fr-FR.resx @@ -153,9 +153,6 @@ Niveau de confiance automatique - - Avatar par défaut activé - Dimensions de l’avatar @@ -979,9 +976,6 @@ Votre méthode de suppression est actuellement définie sur {0}. Vos sujets ser Sélectionnez les rôles qui seront automatiquement souscrits et recevrez des notifications par courriel pour tout nouveau contenu publié sur ce forum. - - Activez cette option pour afficher l’avatar par défaut à partir du répertoire themes lorsqu’un utilisateur n’a pas encore spécifié d’avatar. - Choisissez la hauteur et la largeur maximales des avatars diff --git a/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.it-IT.resx b/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.it-IT.resx index 86ca1f0e4..9765d40d9 100644 --- a/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.it-IT.resx +++ b/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.it-IT.resx @@ -153,9 +153,6 @@ Livello di attendibilità automatica - - Avatar predefinito abilitato - Dimensioni dell'avatar @@ -976,9 +973,6 @@ Il metodo di rimozione è attualmente impostato su {0}. I tuoi argomenti verran Seleziona i ruoli che verranno iscritti automaticamente e riceverai notifiche via e-mail per i nuovi contenuti pubblicati su questo forum. - - Attivare questa opzione per visualizzare l'avatar predefinito dalla directory dei temi quando un utente non ha ancora specificato un avatar. - Scegli l'altezza e la larghezza massime per gli avatar diff --git a/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.nl-NL.resx b/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.nl-NL.resx index d2ab8540a..6d67a8f71 100644 --- a/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.nl-NL.resx +++ b/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.nl-NL.resx @@ -258,9 +258,6 @@ Auto Niveau Vertrouwen - - Standaard Avatar Ingeschakeld - Avatar Dimensies @@ -1126,9 +1123,6 @@ De verwijder-methode is momenteel ingesteld op "{0}". Uw onderwerpen worden {1} Selecteer de rollen welke automatisch zijn geabonneerd en welke notificatie berichten ontvangen wanneer nieuwe berichten in dit forum worden geplaatst. - - Schakel deze optie in om de standaard avatar vanuit de thema directory weer te geven indien een gebruiker geen avatar heeft gespecificeerd. - Kies de maximum hoogte en breedte voor de avatars diff --git a/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.resx b/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.resx index 6c1de919e..b0cf6064f 100644 --- a/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.resx +++ b/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.resx @@ -1077,12 +1077,6 @@ The username you entered was not found. - - Default Avatar Enabled - - - Enable this option to display the default avatar from the themes directory when a user has not yet specified an avatar. - Move Down diff --git a/Dnn.CommunityForums/App_LocalResources/Controlpanel.ascx.de-DE.resx b/Dnn.CommunityForums/App_LocalResources/Controlpanel.ascx.de-DE.resx index ac6943e34..1fc250e0e 100644 --- a/Dnn.CommunityForums/App_LocalResources/Controlpanel.ascx.de-DE.resx +++ b/Dnn.CommunityForums/App_LocalResources/Controlpanel.ascx.de-DE.resx @@ -258,9 +258,6 @@ Automatische Vertrauensstufe - - Standard-Avatar aktiviert - Avatar-Abmessungen @@ -1081,9 +1078,6 @@ Ihre Entfernungsmethode ist derzeit auf {0} eingestellt. Ihre Themen werden anh Wählen Sie Rollen aus, die automatisch abonniert werden, und erhalten Sie E-Mail-Benachrichtigungen für neue Inhalte, die in diesem Forum veröffentlicht werden. - - Aktivieren Sie diese Option, um den Standard-Avatar aus dem Themenverzeichnis anzuzeigen, wenn ein Benutzer noch keinen Avatar angegeben hat. - Wählen Sie die maximale Höhe und Breite für Avatare diff --git a/Dnn.CommunityForums/App_LocalResources/Controlpanel.ascx.es-ES.resx b/Dnn.CommunityForums/App_LocalResources/Controlpanel.ascx.es-ES.resx index 2b95b0860..c47055590 100644 --- a/Dnn.CommunityForums/App_LocalResources/Controlpanel.ascx.es-ES.resx +++ b/Dnn.CommunityForums/App_LocalResources/Controlpanel.ascx.es-ES.resx @@ -153,9 +153,6 @@ Nivel de confianza automática - - Avatar predeterminado habilitado - Dimensiones del avatar @@ -976,9 +973,6 @@ El método de eliminación está configurado actualmente en {0}. Los temas se { Seleccione los roles que se suscribirán automáticamente y recibirán notificaciones por correo para el nuevo contenido publicado en este foro. - - Habilite esta opción para mostrar el avatar predeterminado del directorio de temas cuando un usuario aún no haya especificado un avatar. - Seleccione el alto y ancho máximos de los avatares diff --git a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.de-DE.resx b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.de-DE.resx index 723a9271d..dee29ae89 100644 --- a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.de-DE.resx +++ b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.de-DE.resx @@ -495,4 +495,10 @@ Dies ist der Text, der als Teil der URL verwendet wird, um Likes im Forum zu identifizieren. + + Deaktiviert + + + Gravatar + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.es-ES.resx b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.es-ES.resx index 51ec10b81..0b811fd7d 100644 --- a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.es-ES.resx +++ b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.es-ES.resx @@ -390,4 +390,10 @@ Este es el texto que se utilizará como parte de la URL para identificar los Me gusta del foro. + + Deshabilitado + + + Gravatar + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.fr-FR.resx b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.fr-FR.resx index 9130eb467..469fe65ec 100644 --- a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.fr-FR.resx +++ b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.fr-FR.resx @@ -390,4 +390,10 @@ Il s’agit du texte qui sera utilisé dans le cadre de l’URL pour identifier les likes du forum. + + Désactivé + + + Gravatar + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.it-IT.resx b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.it-IT.resx index 39f80040a..9706723c2 100644 --- a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.it-IT.resx +++ b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.it-IT.resx @@ -390,4 +390,16 @@ Questo è il testo che verrà utilizzato come parte dell'URL per identificare i Mi piace del forum. + + Gravatar + + + Aggiorna avatar + + + Aggiorna periodicamente gli avatar utilizzando il servizio selezionato + + + Disabile + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.nl-NL.resx b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.nl-NL.resx index 9f7c0df08..285301cf8 100644 --- a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.nl-NL.resx +++ b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.nl-NL.resx @@ -390,4 +390,16 @@ Dit is de tekst die zal worden gebruikt als onderdeel van de URL om forum-likes te identificeren. + + Gravatar + + + Avatars vernieuwen + + + Vernieuwt periodiek avatars met behulp van de geselecteerde service + + + Uitgeschakeld + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.resx b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.resx index 080a5e47a..578a8edd2 100644 --- a/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.resx +++ b/Dnn.CommunityForums/App_LocalResources/ForumSettings.ascx.resx @@ -495,4 +495,16 @@ This is the text that will be used as part of the URL to identify forum likes. + + Refresh Avatars + + + Gravatar + + + Periodically refreshes avatars using selected service + + + Disabled + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx index 0e21e10c4..673b01a0c 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx @@ -1838,4 +1838,7 @@ Von Datum der Veröffentlichung + + Gravatar für Benutzer-{0} aktualisiert + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx index dd1ec5ef4..029e32c6a 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx @@ -1837,4 +1837,7 @@ De Fecha de publicación + + Gravatar actualizado para el {0} de usuario + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx index 0d8597531..a71a5ce23 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx @@ -1834,4 +1834,7 @@ De, Date de publication + + Gravatar actualisé pour le {0} utilisateur + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx index 5f5bcb14a..1828cb15d 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx @@ -1837,4 +1837,7 @@ Da Data di pubblicazione + + Gravatar aggiornato per l'utente {0} + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx index bc7961fd5..a823503cf 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx @@ -1873,4 +1873,7 @@ Van, Publicatiedatum + + Gravatar vernieuwd voor {0} gebruiker + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.resx index 5d974627a..1df24960e 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.resx @@ -1834,4 +1834,7 @@ From, Post Date + + Gravatar refreshed for user {0} + \ No newline at end of file diff --git a/Dnn.CommunityForums/Classic.ascx.cs b/Dnn.CommunityForums/Classic.ascx.cs index 56f56cbb0..7bff1b670 100644 --- a/Dnn.CommunityForums/Classic.ascx.cs +++ b/Dnn.CommunityForums/Classic.ascx.cs @@ -60,6 +60,8 @@ protected override void OnLoad(EventArgs e) //ForumsConfig.Sort_PermissionSets_080200(); //ForumsConfig.Upgrade_PermissionSets_090000(); //DotNetNuke.Modules.ActiveForums.Helpers.UpgradeModuleSettings.DeleteObsoleteModuleSettings_090000(); + DotNetNuke.Modules.ActiveForums.Helpers.UpgradeModuleSettings.AddAvatarModuleSettings_090100(); + #endif diff --git a/Dnn.CommunityForums/DnnCommunityForums.dnn b/Dnn.CommunityForums/DnnCommunityForums.dnn index 9f04cea88..f57827613 100644 --- a/Dnn.CommunityForums/DnnCommunityForums.dnn +++ b/Dnn.CommunityForums/DnnCommunityForums.dnn @@ -424,10 +424,15 @@ 09.00.00.SqlDataProvider 09.00.00 + diff --git a/Dnn.CommunityForums/Entities/ForumUserInfo.cs b/Dnn.CommunityForums/Entities/ForumUserInfo.cs index 05dc7e02c..8c7777d53 100644 --- a/Dnn.CommunityForums/Entities/ForumUserInfo.cs +++ b/Dnn.CommunityForums/Entities/ForumUserInfo.cs @@ -101,6 +101,12 @@ public ForumUserInfo(int moduleId, DotNetNuke.Entities.Users.UserInfo userInfo) public string UserCaption { get; set; } + public DateTime? AvatarLastRefresh { get; set; } + + public DateTime? AvatarSourceLastModified { get; set; } + + public int? AvatarFileId { get; set; } + [IgnoreColumn] public DateTime? DateCreated => this.UserInfo?.CreatedOnDate; @@ -123,8 +129,12 @@ public ForumUserInfo(int moduleId, DotNetNuke.Entities.Users.UserInfo userInfo) public bool AttachDisabled { get; set; } + [IgnoreColumn] + [Obsolete("Deprecated in Community Forums. Removing in 10.00.00. Not Used.")] public string Avatar { get; set; } + [IgnoreColumn] + [Obsolete("Deprecated in Community Forums. Removing in 10.00.00. Not Used.")] public AvatarTypes AvatarType { get; set; } public bool AvatarDisabled { get; set; } diff --git a/Dnn.CommunityForums/ForumSettings.ascx b/Dnn.CommunityForums/ForumSettings.ascx index d9a36ac8b..ed3e3f51e 100644 --- a/Dnn.CommunityForums/ForumSettings.ascx +++ b/Dnn.CommunityForums/ForumSettings.ascx @@ -112,6 +112,13 @@
  • <%=LocalizeString("Height")%>:
  • <%=LocalizeString("Width")%>:
+
+ + + + + +
diff --git a/Dnn.CommunityForums/ForumSettings.ascx.cs b/Dnn.CommunityForums/ForumSettings.ascx.cs index 13f63ea75..4f89d0953 100644 --- a/Dnn.CommunityForums/ForumSettings.ascx.cs +++ b/Dnn.CommunityForums/ForumSettings.ascx.cs @@ -134,8 +134,8 @@ public override void LoadSettings() Utilities.SelectListItemByValue(this.drpMode, this.Mode); Utilities.SelectListItemByValue(this.drpThemes, this.Theme); - Utilities.SelectListItemByValue(this.rdAutoLinks, this.AutoLink); + Utilities.SelectListItemByValue(this.drpAvatarRefreshType, this.AvatarRefresh); Utilities.SelectListItemByValue(this.drpDeleteBehavior, this.DeleteBehavior); Utilities.SelectListItemByValue(this.drpProfileVisibility, this.ProfileVisibility); Utilities.SelectListItemByValue(this.drpSignatures, this.Signatures); @@ -198,6 +198,7 @@ public override void UpdateSettings() this.ProfileVisibility = Utilities.SafeConvertInt(this.drpProfileVisibility.SelectedValue); this.Signatures = Utilities.SafeConvertInt(this.drpSignatures.SelectedValue); this.UserNameDisplay = this.drpUserDisplayMode.SelectedValue; + this.AvatarRefresh = this.drpAvatarRefreshType.SelectedValue; this.FriendlyURLs = Utilities.SafeConvertBool(this.rdEnableURLRewriter.SelectedValue); var urlSettings = new FriendlyUrlSettings(this.PortalId); diff --git a/Dnn.CommunityForums/ForumSettings.ascx.designer.cs b/Dnn.CommunityForums/ForumSettings.ascx.designer.cs index 13b1fc7de..46c899082 100644 --- a/Dnn.CommunityForums/ForumSettings.ascx.designer.cs +++ b/Dnn.CommunityForums/ForumSettings.ascx.designer.cs @@ -33,6 +33,14 @@ public partial class ForumSettings /// Auto-generated field. /// To modify move field declaration from designer file to code-behind file. /// + protected global::System.Web.UI.WebControls.DropDownList drpAvatarRefreshType; + /// + /// drpForumGroupTemplate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// protected global::System.Web.UI.WebControls.DropDownList drpForumGroupTemplate; /// /// litForumSecurity control. diff --git a/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs b/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs new file mode 100644 index 000000000..92bef9187 --- /dev/null +++ b/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs @@ -0,0 +1,190 @@ +// Copyright (c) by DNN Community +// +// DNN Community licenses this file to you under the MIT license. +// +// See the LICENSE file in the project root for more information. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +// to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using DotNetNuke.Common.Utilities; + +namespace DotNetNuke.Modules.ActiveForums.Services.Avatars +{ + using System; + using System.Collections.Generic; + using System.Linq; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Services.Log.EventLog; + using DotNetNuke.Services.Scheduling; + + public class AvatarRefreshQueue : DotNetNuke.Services.Scheduling.SchedulerClient + { + public AvatarRefreshQueue(ScheduleHistoryItem scheduleHistoryItem) + { + this.ScheduleHistoryItem = scheduleHistoryItem; + } + + public override void DoWork() + { + try + { + foreach (DotNetNuke.Abstractions.Portals.IPortalInfo portal in DotNetNuke.Entities.Portals.PortalController.Instance.GetPortals()) + { + foreach (ModuleInfo module in DotNetNuke.Entities.Modules.ModuleController.Instance.GetModules(portal.PortalId)) + { + if (module.DesktopModule.ModuleName.Trim().ToLowerInvariant().Equals(Globals.ModuleName.ToLowerInvariant())) + { + if (ForumBase.GetModuleSettings(module.ModuleID).AvatarRefreshEnabled) + { + + if (ForumBase.GetModuleSettings(module.ModuleID).AvatarRefreshType.Equals(Globals.AvatarRefreshGravatar)) + { + var intQueueCount = RefreshGravatars(module.PortalID, module.ModuleID); + this.ScheduleHistoryItem.Succeeded = true; + this.ScheduleHistoryItem.AddLogNote(string.Concat("Processed ", intQueueCount, " avatar refresh requests")); + } + } + } + } + } + } + catch (Exception ex) + { + this.ScheduleHistoryItem.Succeeded = false; + this.ScheduleHistoryItem.AddLogNote(string.Concat("Avatar Refresh Queue Failed. ", ex)); + this.Errored(ref ex); + DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); + } + } + + private static int RefreshGravatars(int portalId, int moduleId) + { + var intQueueCount = 0; + try + { + var avatarProvider = new GravatarAvatarProvider(); + GetBatch(portalId, moduleId).ForEach(forumUser => + { + var completed = RefreshAvatar(portalId: portalId, moduleId: moduleId, avatarProvider: avatarProvider, forumUser: forumUser); + if (completed) + { + intQueueCount += 1; + } + }); + return intQueueCount; + } + catch (Exception ex) + { + DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); + return -1; + } + } + + private static List GetBatch(int portalId, int moduleId) + { + try + { + return new DotNetNuke.Modules.ActiveForums.Controllers.ForumUserController(moduleId: moduleId).Get().Where(u => (u.PortalId == portalId && !u.AvatarDisabled) && + ((string.IsNullOrEmpty(u.UserInfo.Profile.GetPropertyValue("Photo")) && !u.AvatarLastRefresh.HasValue) || /* anyone without an avatar who has never had their avatar refreshed */ + (u.AvatarLastRefresh.HasValue && DateTime.UtcNow.Subtract(u.AvatarLastRefresh.Value).TotalDays > 90))) /* or anyone whose avatar was last refreshed more than 90 days ago */ + .OrderByDescending(u => u.AvatarLastRefresh).Take(50).ToList(); + } + catch (Exception ex) + { + DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); + return null; + } + } + + private static bool RefreshAvatar(int portalId, int moduleId, DotNetNuke.Modules.ActiveForums.Services.Avatars.IAvatarProvider avatarProvider, DotNetNuke.Modules.ActiveForums.Entities.ForumUserInfo forumUser) + { + try + { + if (forumUser == null || string.IsNullOrEmpty(forumUser.Email)) + { + return false; + } + + // If the user already has an avatar and it was not updated via this refresh process, they updated themselves, so skip them + if (!string.IsNullOrEmpty(forumUser.UserInfo.Profile.GetPropertyValue("Photo"))) + { + var usersAvatarFileId = Utilities.SafeConvertInt(forumUser.UserInfo.Profile.GetPropertyValue("Photo"), DotNetNuke.Common.Utilities.Null.NullInteger); + if (!forumUser.AvatarFileId.HasValue || usersAvatarFileId != forumUser.AvatarFileId.Value) + { + forumUser.AvatarLastRefresh = DateTime.UtcNow; + new DotNetNuke.Modules.ActiveForums.Controllers.ForumUserController(moduleId: moduleId).Update(forumUser); + return false; + } + } + + var (contentType, avatarImage, lastModifiedDateTime) = avatarProvider.GetAvatarImageAsync(forumUser.Email).GetAwaiter().GetResult(); + if (avatarImage != null && avatarImage.Length > 0) + { + // Save avatar image to correct image type file based on contentType + var fileExtension = ".img"; + switch (contentType.ToLowerInvariant()) + { + case "image/png": + fileExtension = ".png"; + break; + case "image/jpeg": + case "image/jpg": + fileExtension = ".jpg"; + break; + case "image/gif": + fileExtension = ".gif"; + break; + case "image/bmp": + fileExtension = ".bmp"; + break; + case "image/webp": + fileExtension = ".webp"; + break; + // Add more types as needed + } + + var fileName = $"avatar_{forumUser.UserInfo.UserID}_{DateTime.UtcNow:yyyyMMddHHmmss}{fileExtension}"; + + var userFolder = DotNetNuke.Services.FileSystem.FolderManager.Instance.GetUserFolder(forumUser.UserInfo); + var avatarFile = DotNetNuke.Services.FileSystem.FileManager.Instance.AddFile(folder: userFolder, fileName: fileName, fileContent: new System.IO.MemoryStream(avatarImage), overwrite: true, checkPermissions: false, contentType: contentType, createdByUserID: forumUser.UserId); + + forumUser.UserInfo.Profile.SetProfileProperty("Photo", avatarFile.FileId.ToString()); + + DotNetNuke.Entities.Users.UserController.UpdateUser(portalId: portalId, user: forumUser.UserInfo, loggedAction: true); + + var log = new DotNetNuke.Services.Log.EventLog.LogInfo { LogTypeKey = DotNetNuke.Abstractions.Logging.EventLogType.SCHEDULER_EVENT_PROGRESSING.ToString() }; + log.LogProperties.Add(new LogDetailInfo("Module", Globals.ModuleFriendlyName)); + var message = string.Format(Utilities.GetSharedResource("[RESX:GravatarRefreshed]"), forumUser.UserInfo.DisplayName); + log.AddProperty("Message", message); + DotNetNuke.Services.Log.EventLog.LogController.Instance.AddLog(log); + + forumUser.AvatarSourceLastModified = lastModifiedDateTime; + forumUser.AvatarFileId = avatarFile.FileId; // Set the avatar file ID to the new file created + } + + forumUser.AvatarLastRefresh = DateTime.UtcNow; + new DotNetNuke.Modules.ActiveForums.Controllers.ForumUserController(moduleId: moduleId).Update(forumUser); + + return true; + } + catch (Exception ex) + { + DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); + } + + return false; + } + } +} diff --git a/Dnn.CommunityForums/Services/Avatars/GravatarAvatarProvider.cs b/Dnn.CommunityForums/Services/Avatars/GravatarAvatarProvider.cs new file mode 100644 index 000000000..cbcf84ae1 --- /dev/null +++ b/Dnn.CommunityForums/Services/Avatars/GravatarAvatarProvider.cs @@ -0,0 +1,103 @@ +// Copyright (c) by DNN Community +// +// DNN Community licenses this file to you under the MIT license. +// +// See the LICENSE file in the project root for more information. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +// to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Linq; + +namespace DotNetNuke.Modules.ActiveForums.Services.Avatars +{ + using System; + using System.Net.Http; + using System.Threading.Tasks; + + internal class GravatarAvatarProvider : IAvatarProvider + { + private static readonly HttpClient httpClient = new HttpClient(); + + /// + /// Fetches the avatar image as a byte array. + /// + /// A representing the asynchronous operation. + public async Task<(string ContentType, byte[] ImageBytes, DateTime? LastModifiedDateTime)> GetAvatarImageAsync(string email) + { + var url = GetAvatarUrl(email); + if (!string.IsNullOrEmpty(url)) + { + using (var response = await httpClient.GetAsync(url).ConfigureAwait(false)) + { + try + { + response.EnsureSuccessStatusCode(); + DateTime? lastModifiedDateTime = null; + var lastModifiedHeader = response.Content.Headers.Contains("Last-Modified") + ? string.Join(",", response.Content.Headers.GetValues("Last-Modified")) + : null; + if (!string.IsNullOrEmpty(lastModifiedHeader)) + { + lastModifiedDateTime = DateTime.ParseExact(lastModifiedHeader, "R", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AdjustToUniversal); + if (lastModifiedDateTime > new DateTime(1984, 1, 12)) /* default generic gravatar date */ + { + var contentType = response.Content.Headers.ContentType?.MediaType; + var bytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + if (!bytes.SequenceEqual(ConvertBase64ToByteArray(GravatarGenericAvatarAsBase64()))) + { + return (contentType, bytes, lastModifiedDateTime); + } + } + } + } + catch (Exception ex) + { + DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); + } + } + } + + return (null, null, null); + } + + private static string GetAvatarUrl(string email) + { + if (string.IsNullOrWhiteSpace(email)) + { + DotNetNuke.Services.Exceptions.Exceptions.LogException(new ArgumentException("Email must be provided.", nameof(email))); + return null; + } + + var hash = Utilities.GetSha256Hash(email.Trim().ToLowerInvariant()); + return $"https://www.gravatar.com/avatar/{hash}"; + } + + private static byte[] ConvertBase64ToByteArray(string base64String) + { + // Remove potential Base64 image header if present + if (base64String.Contains(",")) + { + base64String = base64String.Split(',')[1]; + } + + return Convert.FromBase64String(base64String); + } + + private static string GravatarGenericAvatarAsBase64() + { + return "/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gOTAK/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBAUKBwcGCAwKDAwLCgsLDQ4SEA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFBAUJBQUJFA0LDRQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU/8AAEQgAUABQAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A+d6KKK/rM/IgooooAKK9h+Af7L/ir9oF7y40qS20zRrNxFPqV5u2GTGfLRVGWYAgnoACMnkZ9Z13/gmz47soWk0rxDompuoz5UhlgZvYZVh+ZFeDiM9y7C1nQrVUpLp29eiO6ngcRVh7SEW0fItFdr8R/gv41+E10IfFPh670tHbbHdFRJbyH0WVSUJ9s59q4qvYo1qWIgqlKSlF9U7o5JwlTfLNWYUUUVsQFFFdr8FvhxP8Wfih4e8LQlkS/uQLiVOscCgvKw9witj3xWNatDD0pVqjtGKbfoi6cHUkoR3Z7N+y3+xrdfG6wfxH4iu7rRPC24x2xtlUT3jA4YoWBCoDxuIOTkAcEi/+1V+xlb/BLw1H4q8Nand6loaSpDdwahtM1uXOEcOoUMpbC4wCCR1zx+j+haJY+G9HstJ023S00+zhWCCCMYVEUYUD8BXxZ/wUG+P1gNIk+F+lEXN9LJDcarMD8sCqRJHF/vkhGPoAPXj8ey7PcyzPNo+xfuN6x6KPVvz8+/3H2GIwGGwuEfP8XfzPS/8AgnxGifs7WrKoDNqV0Wx3OVGfyAr6XzgZPFfNf/BPrP8AwznZY/6CN1/6EK0P25fEWq+FPgVNqejajdaXqEOpWpS5s5mikX5j0YHOPbvXy+YYd4vOatCLs5Tav6s9bD1FRwUajW0U/wAD3TXNDsPEml3OnapZQajYXKFJra5jEkci+hU8Gvzf/a8/ZCPwkMnizwnHLP4RlkAuLViXfTnY4HPVoyTgE8g4BzkGvdf2Pf2wp/indx+DfGckY8SiMtZaiqhFvwoyyMo4EgAJ44YA8Ajn6r1zRLHxJpF9pWo26Xdhewtb3EEgysiMMMD+Brpw+Ix3DGO9nU+a6SXdfo/kY1KeHzShzR36Pqn2Z+G9Fdr8Z/hxP8Jfif4g8KzFnSwuSIJW6yQMA8TH3KMuffNcVX9AUa0MRSjVpu8ZJNejPz6cHTk4S3QV9df8E2dCivfiz4g1SRQz2OklIyR91pJUGfyRh+NfItfXX/BNnXYrL4s+INLkYK19pRePP8TRyocfk7H8K+f4l5v7Jr8m9vwur/gehltvrUL9z9EtY1KPR9Ivb6UZitYHncD0VSx/lX4i+J/EN54t8R6nreoSGW+1C5kupmPd3Ysfw5r9utZ02PWNIvbGU4iuoHgcj0ZSp/nX4jeJ/D154S8R6nouoRmK90+5ktZ0PZ0Yqfw4r4fgT2XPX/m0+7X9T3s+5rQ7a/ofpX/wT2uop/2eoo45Vd4dTukkQHlGJVgD+DA/jR/wUJuYoP2epY5JVR5tTtkjUnl2BZiB+Ck/hXwJ8Ifj34y+B97cz+F9RSKC6IM9lcoJbeUjoSp6EeoIPvR8Xvj34y+OF9bTeKNRSWC2z9nsraMRW8JPUhR1J9SSfevQ/wBWMT/bP13mXs+bm8+9rf8ABOX+1KX1L2FnzWt5epx/hnxBeeE/EWma1p0phvdOuY7qBx2dGDD8OK/bnRdSj1nSLG/iH7q6gSdAewZQw/nX4jeGfD154t8RaZounxGW+1C5jtYEA6u7BR+HNftzoumxaLo9jp8R/dWsCQJn0VQo/lXn8d+z56Fvi1+7T9djqyHmtPtp95+dv/BSbQorH4s+H9VjUK99pQSQj+Jo5XGfydR+FfItfXX/AAUm12K9+LPh/So2DPY6SHkAP3Wklc4/JFP418i19xw3zf2TQ597fhd2/A8HMrfWp27hXa/Bj4jz/CX4n+H/ABVCGdLC5BniXrJAwKSqPcozY98VxVFfQVqMMRSlSqK8ZJp+jPPp1HTkpx3R+5Oh63Y+JNIsdV065S7sL2FJ7eeM5WRGGVI/A18qfthfsez/ABTupPGXg2OMeJxGFvdPZgi34UYVlY8CQAAc4DADkEc+E/shftfH4SGPwn4skln8IyyE290oLvpzscnjq0ZJyQOQckZyRX6QaHrlh4k0u31HS72DUbC4QPDc20gkjkX1DDg1/P8AiMNjuGMd7Snt0fSS7P8AVfcfoNOph81w/LLfquqfkfiV4h8Mav4S1KXT9a0y70q+iOHgvIWicfgwHHvR4e8M6v4t1KLTtE0y71W9kOEt7KBpXP4KDx71+3GpaLYazAIr+xtr6Ic7LmJZF/JgaXTdFsNFhMWn2NtYxHnZbxLGv5KBX1H+vc/Z29h73rp+V/keZ/YK5vj09NfzPlD9j39j24+Ft5H4z8ZxxnxKYytnpyMHWwDDDOzDgyEEjjhQTySePqvXtbsfDej3uq6jcJaWFnC1xcTyHCoijLE/gKXXNc0/w1pdzqWqXsGnWFuhea5upBHHGvqWPAr83/2vf2vm+LjSeE/CcksHhGKQG4umBR9QdTkcdVjBGQDyTgnGAK+Yw+Hx3FGO9pU+b6RXZfovvPTqVMPldDljv0XVvueF/Gn4jz/Fn4oeIfFMwZEv7km3ifrHAoCRKfcIq5981xVFFfv9GjDD0o0qatGKSXoj8+nN1JOct2FFFFbEhXa/Dj40+NfhLdGbwt4hu9LR23SWoYSW8h9WiYFCffGfeuKorGtRp4iDp1YqUX0aui6dSVN80HZn13oX/BSfx3ZQCPVfD+iam6jHmxiWBm9zhmH5AUmu/wDBSbx3ewGPSvD+iaY7DHmyiWdl+mWUfmDXyLRXz/8Aq1lPNzewV/nb7r2O/wDtLFWtzs7X4jfGfxr8WrsS+KvEF3qaI26O2LCO3jPqsSgID74z71xVFFfQUaNLDwVOlFRiuiVkcE5yqPmm7sKKKK2IP//Z"; + } + } +} diff --git a/Dnn.CommunityForums/Services/Avatars/IAvatarProvider.cs b/Dnn.CommunityForums/Services/Avatars/IAvatarProvider.cs new file mode 100644 index 000000000..21c37c2cf --- /dev/null +++ b/Dnn.CommunityForums/Services/Avatars/IAvatarProvider.cs @@ -0,0 +1,35 @@ +// Copyright (c) by DNN Community +// +// DNN Community licenses this file to you under the MIT license. +// +// See the LICENSE file in the project root for more information. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +// to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace DotNetNuke.Modules.ActiveForums.Services.Avatars +{ + using System; + using System.Threading.Tasks; + + internal interface IAvatarProvider + { + /// + /// Gets the content type and avatar image for the specified email address. + /// + /// The user's email address. + /// content type and avatar image bytes + Task<(string ContentType, byte[] ImageBytes, DateTime? LastModifiedDateTime)> GetAvatarImageAsync(string email); + } +} diff --git a/Dnn.CommunityForums/class/Globals.cs b/Dnn.CommunityForums/class/Globals.cs index c1b3562ef..3464c142c 100644 --- a/Dnn.CommunityForums/class/Globals.cs +++ b/Dnn.CommunityForums/class/Globals.cs @@ -57,6 +57,7 @@ public enum HTMLPermittedUsers Administrators, } + [Obsolete("Deprecated in Community Forums. Removing in 10.00.00. Not Used.")] public enum AvatarTypes { LocalFile, @@ -149,6 +150,8 @@ public static string DefaultAnonRoles public const string ModuleImagesPath = Globals.ModulePath + "images/"; public const string TemplatesPath = Globals.ModulePath + "templates/"; public const string ThemesPath = Globals.ModulePath + "themes/"; + + public const string AvatarRefreshGravatar = "GRAVATAR"; public const string AdminResourceFile = Globals.ModulePath + "App_LocalResources/AdminResources.resx"; public const string SharedResourceFile = Globals.ModulePath + "App_LocalResources/SharedResources.resx"; @@ -189,10 +192,14 @@ public class SettingKeys public const string UserNameDisplay = "USERNAMEDISPLAY"; public const string DisableUserProfiles = "DISABLEUSERPROFILES"; public const string ProfileTabId = "PROFILETABID"; + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. Not Used.")] public const string AllowAvatars = "ALLOWAVATARS"; + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. Not Used.")] public const string AllowAvatarLinks = "ALLOWAVATARLINKS"; + public const string AvatarRefresh = "AVATARREFRESH"; public const string AvatarHeight = "AVATARHEIGHT"; public const string AvatarWidth = "AVATARWIDTH"; + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. Not Used.")] public const string AvatarDefault = "AVATARDEFAULT"; public const string AllowSignatures = "ALLOWSIGNATURES"; public const string StatsEnabled = "STATSENABLED"; diff --git a/Dnn.CommunityForums/class/Settings.cs b/Dnn.CommunityForums/class/Settings.cs index 4d78b89ce..24a6b3c46 100644 --- a/Dnn.CommunityForums/class/Settings.cs +++ b/Dnn.CommunityForums/class/Settings.cs @@ -96,6 +96,16 @@ public int AvatarWidth get { return this.MainSettings.GetInt(SettingKeys.AvatarWidth, 80); } } + public bool AvatarRefreshEnabled + { + get { return this.MainSettings.GetString(SettingKeys.AvatarRefresh) == Globals.AvatarRefreshGravatar; } + } + + public string AvatarRefreshType + { + get { return this.MainSettings.GetString(SettingKeys.AvatarRefresh); } + } + public int AllowSignatures { get { return this.MainSettings.GetInt(SettingKeys.AllowSignatures); } diff --git a/Dnn.CommunityForums/class/Utilities.cs b/Dnn.CommunityForums/class/Utilities.cs index 75defe561..7a5ba5290 100644 --- a/Dnn.CommunityForums/class/Utilities.cs +++ b/Dnn.CommunityForums/class/Utilities.cs @@ -27,6 +27,7 @@ namespace DotNetNuke.Modules.ActiveForums using System.IO; using System.Linq; using System.Reflection; + using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Web; @@ -1716,5 +1717,21 @@ public static bool IsNumeric(object expression) { return expression != null && (double.TryParse(expression.ToString(), out _) || bool.TryParse(expression.ToString(), out _)); } + + internal static string GetSha256Hash(string input) + { + using (var sha256Hash = SHA256.Create()) + { + var inputBytes = Encoding.UTF8.GetBytes(input); + var hashBytes = sha256Hash.ComputeHash(inputBytes); + var sb = new StringBuilder(); + foreach (var b in hashBytes) + { + sb.Append(b.ToString("x2")); + } + + return sb.ToString(); + } + } } } diff --git a/Dnn.CommunityForums/components/Common/ForumSettingsBase.cs b/Dnn.CommunityForums/components/Common/ForumSettingsBase.cs index 728e5e6c1..4fa3890f0 100644 --- a/Dnn.CommunityForums/components/Common/ForumSettingsBase.cs +++ b/Dnn.CommunityForums/components/Common/ForumSettingsBase.cs @@ -459,7 +459,7 @@ public int AvatarHeight this.UpdateModuleSettingCaseSensitive(SettingKeys.AvatarHeight, value.ToString()); } } - + public int AvatarWidth { get @@ -473,6 +473,19 @@ public int AvatarWidth } } + public string AvatarRefresh + { + get + { + return this.Settings.GetString(SettingKeys.AvatarRefresh, Globals.AvatarRefreshGravatar); + } + + set + { + this.UpdateModuleSettingCaseSensitive(SettingKeys.AvatarRefresh, value.ToString()); + } + } + public bool EnableUsersOnline { get diff --git a/Dnn.CommunityForums/components/Helpers/UpgradeModuleSettings.cs b/Dnn.CommunityForums/components/Helpers/UpgradeModuleSettings.cs index 35f07d7e8..7b520ea91 100644 --- a/Dnn.CommunityForums/components/Helpers/UpgradeModuleSettings.cs +++ b/Dnn.CommunityForums/components/Helpers/UpgradeModuleSettings.cs @@ -313,5 +313,41 @@ internal static void DeleteObsoleteModuleSettings_090000() } } } + + internal static void DeleteObsoleteModuleSettings_090100() + { + /* remove TIMEZONEOFFSE, AMFORUMS, MAILQUEUE */ + + foreach (DotNetNuke.Abstractions.Portals.IPortalInfo portal in DotNetNuke.Entities.Portals.PortalController.Instance.GetPortals()) + { + foreach (ModuleInfo module in DotNetNuke.Entities.Modules.ModuleController.Instance.GetModules(portal.PortalId)) + { + if (module.DesktopModule.ModuleName.Trim().ToLowerInvariant().Equals(Globals.ModuleName.ToLowerInvariant())) + { + DotNetNuke.Entities.Modules.ModuleController.Instance.DeleteModuleSetting(module.ModuleID, "ALLOWAVATARS"); + DotNetNuke.Entities.Modules.ModuleController.Instance.DeleteModuleSetting(module.ModuleID, "ALLOWAVATARLINKS"); + DotNetNuke.Entities.Modules.ModuleController.Instance.DeleteModuleSetting(module.ModuleID, "AVATARDEFAULT"); + } + } + } + } + + internal static void AddAvatarModuleSettings_090100() + { + foreach (DotNetNuke.Abstractions.Portals.IPortalInfo portal in DotNetNuke.Entities.Portals.PortalController.Instance.GetPortals()) + { + foreach (ModuleInfo module in DotNetNuke.Entities.Modules.ModuleController.Instance.GetModules(portal.PortalId)) + { + if (module.DesktopModule.ModuleName.Trim().ToLowerInvariant().Equals(Globals.ModuleName.ToLowerInvariant())) + { + DotNetNuke.Entities.Modules.ModuleController.Instance.UpdateModuleSetting(module.ModuleID, SettingKeys.AvatarRefresh, Globals.AvatarRefreshGravatar); + DotNetNuke.Modules.ActiveForums.DataCache.SettingsCacheClear(module.ModuleID, string.Format(DataCache.ModuleSettingsCacheKey, module.TabID)); + DotNetNuke.Modules.ActiveForums.DataCache.SettingsCacheClear(module.ModuleID, string.Format(DataCache.TabModuleSettingsCacheKey, module.TabID)); + DotNetNuke.Modules.ActiveForums.DataCache.ClearAllCacheForTabId(module.TabID); + DotNetNuke.Modules.ActiveForums.DataCache.ClearAllCache(module.ModuleID); + } + } + } + } } } diff --git a/Dnn.CommunityForums/components/Topics/TopicsController.cs b/Dnn.CommunityForums/components/Topics/TopicsController.cs index d11b05916..b7f8ac5a4 100644 --- a/Dnn.CommunityForums/components/Topics/TopicsController.cs +++ b/Dnn.CommunityForums/components/Topics/TopicsController.cs @@ -311,6 +311,20 @@ public string UpgradeModule(string Version) return "Failed"; } + break; + case "09.01.00": + try + { + DotNetNuke.Modules.ActiveForums.Helpers.UpgradeModuleSettings.DeleteObsoleteModuleSettings_090100(); + DotNetNuke.Modules.ActiveForums.Helpers.UpgradeModuleSettings.AddAvatarModuleSettings_090100(); + } + catch (Exception ex) + { + LogError(ex.Message, ex); + Exceptions.LogException(ex); + return "Failed"; + } + break; default: break; diff --git a/Dnn.CommunityForums/config/defaultsetup.config b/Dnn.CommunityForums/config/defaultsetup.config index 03675235d..09ddf467b 100644 --- a/Dnn.CommunityForums/config/defaultsetup.config +++ b/Dnn.CommunityForums/config/defaultsetup.config @@ -16,10 +16,9 @@ - - + @@ -35,7 +34,7 @@ - + diff --git a/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider new file mode 100644 index 000000000..eb3f5008d --- /dev/null +++ b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider @@ -0,0 +1,128 @@ +SET NOCOUNT ON +GO + +/* issue 1411 begin -- updates to activeforums_UserProfiles for avatar updates */ + + +/* recreate activeforums_UserProfiles_Opt2 */ +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{objectQualifier}activeforums_UserProfiles') AND name = N'idx_{objectQualifier}activeforums_UserProfiles_Opt2') +DROP INDEX [idx_{objectQualifier}activeforums_UserProfiles_Opt2] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles +GO +CREATE NONCLUSTERED INDEX [idx_{objectQualifier}activeforums_UserProfiles_Opt2] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles +( + [UserId] ASC +) +INCLUDE ( [TopicCount], +[ReplyCount], +[ViewCount], +[AnswerCount], +[RewardPoints], +[UserCaption], +[DateCreated], +[DateLastActivity], +[Signature], +[SignatureDisabled], +[AvatarDisabled] +) WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) +GO + + +/* activeforums_UserProfiles_Opt3 */ +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{objectQualifier}activeforums_UserProfiles') AND name = N'idx_{objectQualifier}activeforums_UserProfiles_Opt3') +DROP INDEX [idx_{objectQualifier}activeforums_UserProfiles_Opt3] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles +GO +CREATE NONCLUSTERED INDEX [idx_{objectQualifier}activeforums_UserProfiles_Opt3] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles +( + [PortalId] ASC, + [UserId] ASC +) +INCLUDE ( [ProfileId], +[TopicCount], +[ReplyCount], +[ViewCount], +[AnswerCount], +[RewardPoints], +[UserCaption], +[DateCreated], +[DateUpdated], +[DateLastActivity], +[Signature], +[SignatureDisabled], +[TrustLevel], +[AdminWatch], +[AttachDisabled], +[AvatarDisabled], +[PrefDefaultSort], +[PrefDefaultShowReplies], +[PrefJumpLastPost], +[PrefTopicSubscribe], +[PrefSubscriptionType], +[PrefEmailFormat], +[PrefBlockAvatars], +[PrefBlockSignatures], +[PrefPageSize], +[DateLastPost]) WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) +GO + +/* remove Avatar and AvatarType from activeforums_UserProfiles if they exist */ +IF EXISTS(SELECT * FROM sys.columns WHERE [name] = N'Avatar' AND [object_id] = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_UserProfiles]')) +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_UserProfiles] DROP COLUMN Avatar +GO +IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_{objectQualifier}activeforums_UserProfiles_AvatarType]') AND type = 'D') +ALTER TABLE {databaseOwner}{objectQualifier}activeforums_UserProfiles DROP CONSTRAINT DF_{objectQualifier}activeforums_UserProfiles_AvatarType +GO +IF EXISTS(SELECT * FROM sys.columns WHERE [name] = N'AvatarType' AND [object_id] = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_UserProfiles]')) +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_UserProfiles] DROP COLUMN AvatarType +GO + +/* add AvatarLastRefresh to activeforums_UserProfiles */ +IF NOT EXISTS(SELECT * FROM SYS.COLUMNS WHERE Name = N'AvatarLastRefresh' and Object_ID = Object_ID(N'{databaseOwner}[{objectQualifier}activeforums_UserProfiles]')) +BEGIN + ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_UserProfiles] ADD + [AvatarLastRefresh] [datetime] NULL +END +GO +/* add AvatarFileId to activeforums_UserProfiles */ +IF NOT EXISTS(SELECT * FROM SYS.COLUMNS WHERE Name = N'AvatarFileId' and Object_ID = Object_ID(N'{databaseOwner}[{objectQualifier}activeforums_UserProfiles]')) +BEGIN + ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_UserProfiles] ADD + [AvatarFileId] [int] NULL +END +GO +/* add AvatarSourceLastModified to activeforums_UserProfiles */ +IF NOT EXISTS(SELECT * FROM SYS.COLUMNS WHERE Name = N'AvatarSourceLastModified' and Object_ID = Object_ID(N'{databaseOwner}[{objectQualifier}activeforums_UserProfiles]')) +BEGIN + ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_UserProfiles] ADD + [AvatarSourceLastModified] [datetime] NULL +END +GO + +/* activeforums_UserProfiles_Opt5 -- adding index for LastAvatarRefresh for users */ +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{objectQualifier}activeforums_UserProfiles') AND name = N'idx_{objectQualifier}activeforums_UserProfiles_Opt5') +DROP INDEX [idx_{objectQualifier}activeforums_UserProfiles_Opt5] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles +GO +CREATE NONCLUSTERED INDEX [idx_{objectQualifier}activeforums_UserProfiles_Opt5] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles +( + [PortalId] ASC, + [AvatarDisabled], + [AvatarLastRefresh] DESC, + [UserId] ASC +) +INCLUDE ( + [AvatarFileId], + [AvatarSourceLastModified] +) +WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) +GO +/* issue 1411 end -- add AvatarLastRefresh */ + +/* --------------------- */ + +/* issue 1412 start -- create scheduler entry for avatar refresh queue */ + +IF NOT EXISTS (Select * From {databaseOwner}{objectQualifier}Schedule WHERE TypeFullName = 'DotNetNuke.Modules.ActiveForums.Services.Avatars.AvatarRefreshQueue, DotNetNuke.Modules.ActiveForums') + INSERT INTO {databaseOwner}{objectQualifier}Schedule (TypeFullName,TimeLapse,TimeLapseMeasurement,RetryTimeLapse,RetryTimeLapseMeasurement,RetainHistoryNum,AttachToEvent,CatchUpEnabled,Enabled,ObjectDependencies,Servers,FriendlyName) + VALUES('DotNetNuke.Modules.ActiveForums.Services.Avatars.AvatarRefreshQueue, DotNetNuke.Modules.ActiveForums',1,'h',1,'h',100,'',1,1,'','','DNN Community Forums Avatar Refresh Queue') + +/* issue 1412 end -- create scheduler entry for avatar refresh queue */ +/* --------------------- */ diff --git a/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider b/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider index 4bafb9438..dffe4189b 100644 --- a/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider +++ b/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider @@ -957,3 +957,14 @@ IF EXISTS (Select * From {databaseOwner}{objectQualifier}Schedule WHERE TypeFull GO /* end 08.01.00 */ + +/* begin 09.01.00 */ + +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_UserProfiles') AND name = N'idx_{objectQualifier}activeforums_UserProfiles_Opt5') +DROP INDEX [idx_{objectQualifier}activeforums_UserProfiles_Opt5] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles +GO +IF EXISTS (Select * From {databaseOwner}{objectQualifier}Schedule WHERE TypeFullName = 'DotNetNuke.Modules.ActiveForums.Services.Avatars.AvatarRefreshQueue, DotNetNuke.Modules.ActiveForums') + DELETE FROM {databaseOwner}{objectQualifier}Schedule WHERE TypeFullName = 'DotNetNuke.Modules.ActiveForums.Services.Avatars.AvatarRefreshQueue, DotNetNuke.Modules.ActiveForums' +GO + +/* end 09.01.00 */ diff --git a/Dnn.CommunityForumsTests/TestBase.cs b/Dnn.CommunityForumsTests/TestBase.cs index 74244c5f0..e29eae258 100644 --- a/Dnn.CommunityForumsTests/TestBase.cs +++ b/Dnn.CommunityForumsTests/TestBase.cs @@ -376,8 +376,7 @@ private void SetupMainSettings() { this.MainSettings.Object.MainSettings = new Hashtable { - { SettingKeys.AllowAvatarLinks, true }, - { SettingKeys.AllowAvatars, true }, + { SettingKeys.AvatarRefresh, "DISABLED" }, { SettingKeys.AllowSignatures, true }, { SettingKeys.AllowSubscribe, true }, { SettingKeys.AnswerPointValue, 1 }, diff --git a/Dnn.CommunityForumsTests/class/UtilitiesTests.cs b/Dnn.CommunityForumsTests/class/UtilitiesTests.cs index 9d8444af3..a6307b1b8 100644 --- a/Dnn.CommunityForumsTests/class/UtilitiesTests.cs +++ b/Dnn.CommunityForumsTests/class/UtilitiesTests.cs @@ -264,5 +264,19 @@ public bool IsNumericTest(object obj) // Assert return actualResult; } + + [Test] + public void GetSha256HashTest() + { + // Arrange + var input = "webmaster@dnncommunity.org"; + var expectedResult = "db4c6321693845e820dfbebbd7df8e5c980f7aa3c9f4a884ad10ee57d0ba159f"; + + // Act + var actualResult = DotNetNuke.Modules.ActiveForums.Utilities.GetSha256Hash(input); + + // Assert + Assert.That(actualResult, Is.EqualTo(expectedResult)); + } } } diff --git a/DnnCommunityForums.sln b/DnnCommunityForums.sln index 176855f43..364b7f0ed 100644 --- a/DnnCommunityForums.sln +++ b/DnnCommunityForums.sln @@ -36,6 +36,7 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution + RESX_AutoApplyExistingTranslations = False SolutionGuid = {DACD6BD0-773E-403D-A41B-38957EE5F01D} EndGlobalSection EndGlobal From bec510ca4db85994583e800686a553970fbf7deb Mon Sep 17 00:00:00 2001 From: John Henley Date: Wed, 25 Jun 2025 22:04:29 +0000 Subject: [PATCH 03/40] FIX: SQL to enforce uniqueness to forums and topics tracking tables --- Dnn.CommunityForums/DnnCommunityForums.dnn | 7 +- .../sql/09.01.00.SqlDataProvider | 116 ++++++++++++++++++ .../sql/Uninstall.SqlDataProvider | 6 + 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 Dnn.CommunityForums/sql/09.01.00.SqlDataProvider diff --git a/Dnn.CommunityForums/DnnCommunityForums.dnn b/Dnn.CommunityForums/DnnCommunityForums.dnn index 9f04cea88..f57827613 100644 --- a/Dnn.CommunityForums/DnnCommunityForums.dnn +++ b/Dnn.CommunityForums/DnnCommunityForums.dnn @@ -424,10 +424,15 @@ 09.00.00.SqlDataProvider 09.00.00 + diff --git a/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider new file mode 100644 index 000000000..a265def00 --- /dev/null +++ b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider @@ -0,0 +1,116 @@ +SET NOCOUNT ON +GO + +/* issues 1434 - begin - duplicated forum and topic tracking */ + +/* Drop indexes for forum and topic tracking tables to avoid duplicates -- note index prefix name change from idx_ to IX_ */ +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Forums_Tracking') AND name = N'idx_{objectQualifier}activeforums_Forums_Tracking_Opt1') +DROP INDEX [idx_{objectQualifier}activeforums_Forums_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Forums_Tracking +GO +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Forums_Tracking') AND name = N'IX_{objectQualifier}activeforums_Forums_Tracking_Opt1') +DROP INDEX [IX_{objectQualifier}activeforums_Forums_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Forums_Tracking +GO + +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Tracking') AND name = N'idx_{objectQualifier}activeforums_Topics_Tracking_Opt1') +DROP INDEX [idx_{objectQualifier}activeforums_Topics_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Topics_Tracking +GO +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Tracking') AND name = N'IX_{objectQualifier}activeforums_Topics_Tracking_Opt1') +DROP INDEX [IX_{objectQualifier}activeforums_Topics_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Topics_Tracking +GO + +/* delete any duplicated tracking records for user/forum, keeping latest one */ +WITH ft_max AS ( +SELECT ft.ForumId, ft.UserId, MAX(TrackingId) AS TrackingId +FROM {databaseOwner}[{objectQualifier}activeforums_Forums_Tracking] ft +GROUP BY ft.ForumId, ft.UserId +) +, ft_multiples AS ( +SELECT ft.ForumId, ft.UserId, ft_max.TrackingId +FROM {databaseOwner}[{objectQualifier}activeforums_Forums_Tracking] ft +LEFT OUTER JOIN ft_max +ON ft_max.ForumId = ft.ForumId +AND ft_max.UserId = ft.UserId +GROUP BY ft.UserId, ft.ForumId, ft_max.TrackingId +HAVING COUNT(*) > 1 +) + +DELETE ft +FROM {databaseOwner}[{objectQualifier}activeforums_Forums_Tracking] ft +INNER JOIN ft_max +ON ft_max.ForumId = ft.ForumId +AND ft_max.UserId = ft.UserId +INNER JOIN ft_multiples +ON ft_multiples.ForumId = ft_max.ForumId +AND ft_multiples.UserId = ft_max.UserId + +WHERE ft.ForumId = ft_max.ForumId +AND ft.UserId = ft_max.UserId +AND ft.TrackingId < ft_max.TrackingId +GO + +/* delete topic tracking records from incorrect (moved) forums */ + +DELETE tt +FROM {databaseOwner}[{objectQualifier}activeforums_Topics_Tracking] tt +LEFT OUTER JOIN {databaseOwner}[{objectQualifier}activeforums_ForumTopics] ft +ON ft.TopicId = tt.TopicId +AND ft.ForumId = tt.ForumId +WHERE ft.TopicId IS NULL +GO + +/* delete any duplicated tracking records for user/topic, keeping latest one */ +WITH tt_max AS ( +SELECT tt.TopicId, tt.UserId, MAX(TrackingId) AS TrackingId +FROM {databaseOwner}[{objectQualifier}activeforums_Topics_Tracking] tt +GROUP BY tt.TopicId, tt.UserId +) +, tt_multiples AS ( +SELECT tt.TopicId, tt.UserId, tt_max.TrackingId +FROM {databaseOwner}[{objectQualifier}activeforums_Topics_Tracking] tt +LEFT OUTER JOIN tt_max +ON tt_max.TopicId = tt.TopicId +AND tt_max.UserId = tt.UserId +GROUP BY tt.UserId, tt.TopicId, tt_max.TrackingId +HAVING COUNT(*) > 1 +) + +DELETE tt +FROM {databaseOwner}[{objectQualifier}activeforums_Topics_Tracking] tt +INNER JOIN tt_max +ON tt_max.TopicId = tt.TopicId +AND tt_max.UserId = tt.UserId +INNER JOIN tt_multiples +ON tt_multiples.TopicId = tt_max.TopicId +AND tt_multiples.UserId = tt_max.UserId + +WHERE tt.TopicId = tt_max.TopicId +AND tt.UserId = tt_max.UserId +AND tt.TrackingId < tt_max.TrackingId +GO + +/* create new indexes */ + +CREATE UNIQUE NONCLUSTERED INDEX [IX_{objectQualifier}activeforums_Topics_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Topics_Tracking +( + [UserId] ASC, + [TopicId] ASC +) +INCLUDE ( +[LastReplyId] +) WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) +GO + +CREATE UNIQUE NONCLUSTERED INDEX [IX_{objectQualifier}activeforums_Forums_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Forums_Tracking +( + [UserId] ASC, + [ForumId] ASC +) +INCLUDE ( +[MaxTopicRead], +[MaxReplyRead] +) WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) +GO + +/* issues 1434 - end - duplicated forum and topic tracking */ + +/* ---------------- */ diff --git a/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider b/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider index 4bafb9438..49582dac0 100644 --- a/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider +++ b/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider @@ -480,6 +480,9 @@ GO IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Tracking') AND name = N'idx_{objectQualifier}activeforums_Topics_Tracking_Opt1') DROP INDEX [idx_{objectQualifier}activeforums_Topics_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Topics_Tracking GO +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Tracking') AND name = N'IX_{objectQualifier}activeforums_Topics_Tracking_Opt1') +DROP INDEX [IX_{objectQualifier}activeforums_Topics_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Topics_Tracking +GO IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Replies') AND name = N'idx_{objectQualifier}activeforums_Replies_Opt1') DROP INDEX [idx_{objectQualifier}activeforums_Replies_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Replies GO @@ -504,6 +507,9 @@ GO IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Forums_Tracking') AND name = N'idx_{objectQualifier}activeforums_Forums_Tracking_Opt1') DROP INDEX [idx_{objectQualifier}activeforums_Forums_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Forums_Tracking GO +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Forums_Tracking') AND name = N'IX_{objectQualifier}activeforums_Forums_Tracking_Opt1') +DROP INDEX [IX_{objectQualifier}activeforums_Forums_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Forums_Tracking +GO IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_UserProfiles') AND name = N'idx_{objectQualifier}activeforums_UserProfiles_Opt1') DROP INDEX [idx_{objectQualifier}activeforums_UserProfiles_Opt1] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles GO From 46cf7d5b03a09fd0042d134e83515ffcc4517127 Mon Sep 17 00:00:00 2001 From: John Henley Date: Thu, 26 Jun 2025 17:01:13 +0000 Subject: [PATCH 04/40] resync --- .../Services/Avatars/AvatarRefreshQueue.cs | 8 ++-- Dnn.CommunityForums/class/ForumsConfig.cs | 42 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs b/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs index c09de4b9b..c878c8237 100644 --- a/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs +++ b/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs @@ -44,7 +44,7 @@ public override void DoWork() { foreach (ModuleInfo module in DotNetNuke.Entities.Modules.ModuleController.Instance.GetModules(portal.PortalId)) { - if (module.DesktopModule.ModuleName.Trim().ToLowerInvariant().Equals(Globals.ModuleName.ToLowerInvariant())) + if (!module.IsDeleted && module.DesktopModule.ModuleName.Trim().ToLowerInvariant().Equals(Globals.ModuleName.ToLowerInvariant())) { if (ForumBase.GetModuleSettings(module.ModuleID).AvatarRefreshEnabled) { @@ -53,7 +53,7 @@ public override void DoWork() { var intQueueCount = RefreshGravatars(module.PortalID, module.ModuleID); this.ScheduleHistoryItem.Succeeded = true; - this.ScheduleHistoryItem.AddLogNote(string.Concat("Processed ", intQueueCount, " avatar refresh requests")); + this.ScheduleHistoryItem.AddLogNote($"Processed {intQueueCount} avatar refresh requests for {module.ModuleTitle} on portal {portal.PortalName}. "); } } } @@ -96,7 +96,7 @@ private static int RefreshGravatars(int portalId, int moduleId) { try { - return new DotNetNuke.Modules.ActiveForums.Controllers.ForumUserController(moduleId: moduleId).Get().Where(u => (u.PortalId == portalId && !u.AvatarDisabled && !u.PrefBlockAvatars) && + return new DotNetNuke.Modules.ActiveForums.Controllers.ForumUserController(moduleId: moduleId).Get().Where(u => (u.PortalId == portalId && !u.UserInfo.IsDeleted && !u.AvatarDisabled && !u.PrefBlockAvatars) && ((string.IsNullOrEmpty(u.UserInfo.Profile.GetPropertyValue("Photo")) && !u.AvatarLastRefresh.HasValue) || /* anyone without an avatar who has never had their avatar refreshed */ (u.AvatarLastRefresh.HasValue && DateTime.UtcNow.Subtract(u.AvatarLastRefresh.Value).TotalDays > 90))) /* or anyone whose avatar was last refreshed more than 90 days ago */ .OrderByDescending(u => u.AvatarLastRefresh).Take(50).ToList(); @@ -164,7 +164,7 @@ private static bool RefreshAvatar(int portalId, int moduleId, DotNetNuke.Modules DotNetNuke.Entities.Users.UserController.UpdateUser(portalId: portalId, user: forumUser.UserInfo, loggedAction: true); - var log = new DotNetNuke.Services.Log.EventLog.LogInfo { LogTypeKey = DotNetNuke.Abstractions.Logging.EventLogType.SCHEDULER_EVENT_PROGRESSING.ToString() }; + var log = new DotNetNuke.Services.Log.EventLog.LogInfo { LogTypeKey = DotNetNuke.Abstractions.Logging.EventLogType.ADMIN_ALERT.ToString() }; log.LogProperties.Add(new LogDetailInfo("Module", Globals.ModuleFriendlyName)); var message = string.Format(Utilities.GetSharedResource("[RESX:GravatarRefreshed]"), forumUser.UserInfo.DisplayName); log.AddProperty("Message", message); diff --git a/Dnn.CommunityForums/class/ForumsConfig.cs b/Dnn.CommunityForums/class/ForumsConfig.cs index 5894c39d1..4fb5a0056 100644 --- a/Dnn.CommunityForums/class/ForumsConfig.cs +++ b/Dnn.CommunityForums/class/ForumsConfig.cs @@ -744,27 +744,27 @@ internal static void Upgrade_PermissionSets_090000() { foreach (var perms in new DotNetNuke.Modules.ActiveForums.Controllers.PermissionController().Get()) { - perms.Announce = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Announce)); - perms.Attach = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Attach)); - perms.Ban = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Ban)); - perms.Block = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Block)); - perms.Categorize = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Categorize)); - perms.Create = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Create)); - perms.Delete = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Delete)); - perms.Edit = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Edit)); - perms.Lock = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Lock)); - perms.Moderate = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Moderate)); - perms.Move = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Move)); - perms.Pin = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Pin)); - perms.Poll = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Poll)); - perms.Prioritize = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Prioritize)); - perms.Read = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Read)); - perms.Reply = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Reply)); - perms.Split = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Split)); - perms.Subscribe = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Subscribe)); - perms.Tag = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Tag)); - perms.Trust = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Trust)); - perms.View = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.View)); + perms.Announce = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Announce) ? string.Empty : perms.Announce.Replace(":", ";"))); + perms.Attach = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Attach) ? string.Empty : perms.Attach.Replace(":", ";"))); + perms.Ban = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Ban) ? string.Empty : perms.Ban.Replace(":", ";"))); + perms.Block = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Block) ? string.Empty : perms.Block.Replace(":", ";"))); + perms.Categorize = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Categorize) ? string.Empty : perms.Categorize.Replace(":", ";"))); + perms.Create = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Create) ? string.Empty : perms.Create.Replace(":", ";"))); + perms.Delete = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Delete) ? string.Empty : perms.Delete.Replace(":", ";"))); + perms.Edit = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Edit) ? string.Empty : perms.Edit.Replace(":", ";"))); + perms.Lock = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Lock) ? string.Empty : perms.Lock.Replace(":", ";"))); + perms.Moderate = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Moderate) ? string.Empty : perms.Moderate.Replace(":", ";"))); + perms.Move = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Move) ? string.Empty : perms.Move.Replace(":", ";"))); + perms.Pin = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Pin) ? string.Empty : perms.Pin.Replace(":", ";"))); + perms.Poll = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Poll) ? string.Empty : perms.Poll.Replace(":", ";"))); + perms.Prioritize = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Prioritize) ? string.Empty : perms.Prioritize.Replace(":", ";"))); + perms.Read = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Read) ? string.Empty : perms.Read.Replace(":", ";"))); + perms.Reply = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Reply) ? string.Empty : perms.Reply.Replace(":", ";"))); + perms.Split = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Split) ? string.Empty : perms.Split.Replace(":", ";"))); + perms.Subscribe = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Subscribe) ? string.Empty : perms.Subscribe.Replace(":", ";"))); + perms.Tag = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Tag) ? string.Empty : perms.Tag.Replace(":", ";"))); + perms.Trust = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Trust) ? string.Empty : perms.Trust.Replace(":", ";"))); + perms.View = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.View) ? string.Empty : perms.View.Replace(":", ";"))); new DotNetNuke.Modules.ActiveForums.Controllers.PermissionController().Update(perms); } From 78ccd66e122d1412c75e74c94a985a5c525e07c7 Mon Sep 17 00:00:00 2001 From: John Henley Date: Thu, 26 Jun 2025 17:01:13 +0000 Subject: [PATCH 05/40] resync --- Dnn.CommunityForums/Classic.ascx.cs | 2 +- .../Services/Avatars/AvatarRefreshQueue.cs | 8 ++-- Dnn.CommunityForums/class/ForumsConfig.cs | 42 +++++++++---------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Dnn.CommunityForums/Classic.ascx.cs b/Dnn.CommunityForums/Classic.ascx.cs index 7bff1b670..c55d2eef7 100644 --- a/Dnn.CommunityForums/Classic.ascx.cs +++ b/Dnn.CommunityForums/Classic.ascx.cs @@ -60,7 +60,7 @@ protected override void OnLoad(EventArgs e) //ForumsConfig.Sort_PermissionSets_080200(); //ForumsConfig.Upgrade_PermissionSets_090000(); //DotNetNuke.Modules.ActiveForums.Helpers.UpgradeModuleSettings.DeleteObsoleteModuleSettings_090000(); - DotNetNuke.Modules.ActiveForums.Helpers.UpgradeModuleSettings.AddAvatarModuleSettings_090100(); + //DotNetNuke.Modules.ActiveForums.Helpers.UpgradeModuleSettings.AddAvatarModuleSettings_090100(); #endif diff --git a/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs b/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs index c09de4b9b..c878c8237 100644 --- a/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs +++ b/Dnn.CommunityForums/Services/Avatars/AvatarRefreshQueue.cs @@ -44,7 +44,7 @@ public override void DoWork() { foreach (ModuleInfo module in DotNetNuke.Entities.Modules.ModuleController.Instance.GetModules(portal.PortalId)) { - if (module.DesktopModule.ModuleName.Trim().ToLowerInvariant().Equals(Globals.ModuleName.ToLowerInvariant())) + if (!module.IsDeleted && module.DesktopModule.ModuleName.Trim().ToLowerInvariant().Equals(Globals.ModuleName.ToLowerInvariant())) { if (ForumBase.GetModuleSettings(module.ModuleID).AvatarRefreshEnabled) { @@ -53,7 +53,7 @@ public override void DoWork() { var intQueueCount = RefreshGravatars(module.PortalID, module.ModuleID); this.ScheduleHistoryItem.Succeeded = true; - this.ScheduleHistoryItem.AddLogNote(string.Concat("Processed ", intQueueCount, " avatar refresh requests")); + this.ScheduleHistoryItem.AddLogNote($"Processed {intQueueCount} avatar refresh requests for {module.ModuleTitle} on portal {portal.PortalName}. "); } } } @@ -96,7 +96,7 @@ private static int RefreshGravatars(int portalId, int moduleId) { try { - return new DotNetNuke.Modules.ActiveForums.Controllers.ForumUserController(moduleId: moduleId).Get().Where(u => (u.PortalId == portalId && !u.AvatarDisabled && !u.PrefBlockAvatars) && + return new DotNetNuke.Modules.ActiveForums.Controllers.ForumUserController(moduleId: moduleId).Get().Where(u => (u.PortalId == portalId && !u.UserInfo.IsDeleted && !u.AvatarDisabled && !u.PrefBlockAvatars) && ((string.IsNullOrEmpty(u.UserInfo.Profile.GetPropertyValue("Photo")) && !u.AvatarLastRefresh.HasValue) || /* anyone without an avatar who has never had their avatar refreshed */ (u.AvatarLastRefresh.HasValue && DateTime.UtcNow.Subtract(u.AvatarLastRefresh.Value).TotalDays > 90))) /* or anyone whose avatar was last refreshed more than 90 days ago */ .OrderByDescending(u => u.AvatarLastRefresh).Take(50).ToList(); @@ -164,7 +164,7 @@ private static bool RefreshAvatar(int portalId, int moduleId, DotNetNuke.Modules DotNetNuke.Entities.Users.UserController.UpdateUser(portalId: portalId, user: forumUser.UserInfo, loggedAction: true); - var log = new DotNetNuke.Services.Log.EventLog.LogInfo { LogTypeKey = DotNetNuke.Abstractions.Logging.EventLogType.SCHEDULER_EVENT_PROGRESSING.ToString() }; + var log = new DotNetNuke.Services.Log.EventLog.LogInfo { LogTypeKey = DotNetNuke.Abstractions.Logging.EventLogType.ADMIN_ALERT.ToString() }; log.LogProperties.Add(new LogDetailInfo("Module", Globals.ModuleFriendlyName)); var message = string.Format(Utilities.GetSharedResource("[RESX:GravatarRefreshed]"), forumUser.UserInfo.DisplayName); log.AddProperty("Message", message); diff --git a/Dnn.CommunityForums/class/ForumsConfig.cs b/Dnn.CommunityForums/class/ForumsConfig.cs index 5894c39d1..4fb5a0056 100644 --- a/Dnn.CommunityForums/class/ForumsConfig.cs +++ b/Dnn.CommunityForums/class/ForumsConfig.cs @@ -744,27 +744,27 @@ internal static void Upgrade_PermissionSets_090000() { foreach (var perms in new DotNetNuke.Modules.ActiveForums.Controllers.PermissionController().Get()) { - perms.Announce = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Announce)); - perms.Attach = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Attach)); - perms.Ban = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Ban)); - perms.Block = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Block)); - perms.Categorize = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Categorize)); - perms.Create = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Create)); - perms.Delete = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Delete)); - perms.Edit = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Edit)); - perms.Lock = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Lock)); - perms.Moderate = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Moderate)); - perms.Move = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Move)); - perms.Pin = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Pin)); - perms.Poll = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Poll)); - perms.Prioritize = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Prioritize)); - perms.Read = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Read)); - perms.Reply = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Reply)); - perms.Split = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Split)); - perms.Subscribe = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Subscribe)); - perms.Tag = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Tag)); - perms.Trust = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.Trust)); - perms.View = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(perms.View)); + perms.Announce = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Announce) ? string.Empty : perms.Announce.Replace(":", ";"))); + perms.Attach = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Attach) ? string.Empty : perms.Attach.Replace(":", ";"))); + perms.Ban = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Ban) ? string.Empty : perms.Ban.Replace(":", ";"))); + perms.Block = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Block) ? string.Empty : perms.Block.Replace(":", ";"))); + perms.Categorize = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Categorize) ? string.Empty : perms.Categorize.Replace(":", ";"))); + perms.Create = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Create) ? string.Empty : perms.Create.Replace(":", ";"))); + perms.Delete = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Delete) ? string.Empty : perms.Delete.Replace(":", ";"))); + perms.Edit = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Edit) ? string.Empty : perms.Edit.Replace(":", ";"))); + perms.Lock = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Lock) ? string.Empty : perms.Lock.Replace(":", ";"))); + perms.Moderate = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Moderate) ? string.Empty : perms.Moderate.Replace(":", ";"))); + perms.Move = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Move) ? string.Empty : perms.Move.Replace(":", ";"))); + perms.Pin = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Pin) ? string.Empty : perms.Pin.Replace(":", ";"))); + perms.Poll = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Poll) ? string.Empty : perms.Poll.Replace(":", ";"))); + perms.Prioritize = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Prioritize) ? string.Empty : perms.Prioritize.Replace(":", ";"))); + perms.Read = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Read) ? string.Empty : perms.Read.Replace(":", ";"))); + perms.Reply = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Reply) ? string.Empty : perms.Reply.Replace(":", ";"))); + perms.Split = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Split) ? string.Empty : perms.Split.Replace(":", ";"))); + perms.Subscribe = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Subscribe) ? string.Empty : perms.Subscribe.Replace(":", ";"))); + perms.Tag = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Tag) ? string.Empty : perms.Tag.Replace(":", ";"))); + perms.Trust = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.Trust) ? string.Empty : perms.Trust.Replace(":", ";"))); + perms.View = DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIds(DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.GetRoleIdsFromPermSet(string.IsNullOrEmpty(perms.View) ? string.Empty : perms.View.Replace(":", ";"))); new DotNetNuke.Modules.ActiveForums.Controllers.PermissionController().Update(perms); } From fa9be148fa2f02e537bf0a3ecb9c5d2c2803da05 Mon Sep 17 00:00:00 2001 From: John Henley Date: Tue, 5 Aug 2025 21:09:45 +0000 Subject: [PATCH 06/40] TASK: update permissions checking in web API methods to use role Ids TASK: refactor Topic view model and topic update editor control --- .../Controllers/PermissionController.cs | 1 + .../ServerControls/TopicsNavigator.cs | 2 +- Dnn.CommunityForums/DnnCommunityForums.csproj | 4 +- Dnn.CommunityForums/DnnCommunityForums.dnn | 8 +- .../DnnCommunityForums_Symbols.dnn | 4 +- Dnn.CommunityForums/ReleaseNotes.txt | 14 +- .../Services/Controllers/ReplyController.cs | 18 +- .../Services/Controllers/TopicController.cs | 158 +++++++------ .../Services/ServicesHelper.cs | 3 +- Dnn.CommunityForums/ViewModels/Topic.cs | 210 +++++++----------- .../Controllers/PermissionControllerTests.cs | 2 +- 11 files changed, 183 insertions(+), 241 deletions(-) diff --git a/Dnn.CommunityForums/Controllers/PermissionController.cs b/Dnn.CommunityForums/Controllers/PermissionController.cs index 2ff6d71a0..547fcfd8c 100644 --- a/Dnn.CommunityForums/Controllers/PermissionController.cs +++ b/Dnn.CommunityForums/Controllers/PermissionController.cs @@ -369,6 +369,7 @@ internal static HashSet GetRoleIdsFromRoleString(string roleString) return roleIds; } + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. Not Used.")] internal static HashSet GetRoleIdsFromRoleIdArray(string[] roles) { return roles.Select(r => Convert.ToInt32(r)).ToHashSet(); diff --git a/Dnn.CommunityForums/CustomControls/ServerControls/TopicsNavigator.cs b/Dnn.CommunityForums/CustomControls/ServerControls/TopicsNavigator.cs index 44ae49169..9e69f3e83 100644 --- a/Dnn.CommunityForums/CustomControls/ServerControls/TopicsNavigator.cs +++ b/Dnn.CommunityForums/CustomControls/ServerControls/TopicsNavigator.cs @@ -103,7 +103,7 @@ protected override void Render(HtmlTextWriter writer) if (this.ForumId > 0) { - if (DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.HasAccess(this.ForumInfo.Security.View, this.ForumUser.UserPermSet)) + if (DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(this.ForumInfo.Security.ViewRoleIds, this.ForumUser.UserRoleIds)) { tb.ForumIds = this.ForumId.ToString(); } diff --git a/Dnn.CommunityForums/DnnCommunityForums.csproj b/Dnn.CommunityForums/DnnCommunityForums.csproj index ea7591004..9696e5d02 100644 --- a/Dnn.CommunityForums/DnnCommunityForums.csproj +++ b/Dnn.CommunityForums/DnnCommunityForums.csproj @@ -12,8 +12,8 @@ Discussion Forum Module for DNN dnncommunity.org dnncommunity.org - 09.00.02.00 - 09.00.02.00 + 09.01.00.00 + 09.01.00.00 False True latest diff --git a/Dnn.CommunityForums/DnnCommunityForums.dnn b/Dnn.CommunityForums/DnnCommunityForums.dnn index 537e320a2..e78b54bdd 100644 --- a/Dnn.CommunityForums/DnnCommunityForums.dnn +++ b/Dnn.CommunityForums/DnnCommunityForums.dnn @@ -1,6 +1,6 @@  - + DNN Community Forums DNN Community Forums: The official online forums module for the DNN Community. DesktopModules/ActiveForums/images/branding/logo/DNN-Community-Forums-Icon-64px.png @@ -86,7 +86,7 @@ DotNetNuke.Modules.ActiveForums.dll bin\DotNetNuke.Modules.ActiveForums.dll - 09.00.02 + 09.01.00 @@ -476,7 +476,7 @@ - + DNN Community Forums What's New ActiveForumsWhatsNew @@ -546,7 +546,7 @@ - + DNN Community Forums Forums Viewer ActiveForumsViewer DNN Community Forums: Display any forum topic view on any page within your site. diff --git a/Dnn.CommunityForums/DnnCommunityForums_Symbols.dnn b/Dnn.CommunityForums/DnnCommunityForums_Symbols.dnn index 2d46d484f..c1e20b09b 100644 --- a/Dnn.CommunityForums/DnnCommunityForums_Symbols.dnn +++ b/Dnn.CommunityForums/DnnCommunityForums_Symbols.dnn @@ -1,6 +1,6 @@  - + DNN Community Forums Symbols DNN Community Forums: The official online forums module for the DNN Community. @@ -15,7 +15,7 @@ True - Active Forums + Active Forums diff --git a/Dnn.CommunityForums/ReleaseNotes.txt b/Dnn.CommunityForums/ReleaseNotes.txt index c822f81fa..98e8b727a 100644 --- a/Dnn.CommunityForums/ReleaseNotes.txt +++ b/Dnn.CommunityForums/ReleaseNotes.txt @@ -15,14 +15,15 @@
-

Important Upgrade Notes

+

Important Upgrade Note

VERY IMPORTANT:The MINIMUM DNN Platform version for this release is now DNN 9.11. +

- +

@@ -47,7 +48,7 @@

- 09.00.02 + 09.01.00

+ +You are a senior .NET developer, experienced in C#, JavaScript, HTML, ASP.NET Framework 4.8, CSS, and SQL. +You understand the priciples of DNN (DotNetNuke) and how to develop DNN modules. +You use Visual Studio Enterprise for running, debugging, and testing DNN (DotNetNuke) modules. + +## Code Style and Structure +- Write idiomatic and efficient C# code. +- Follow .NET conventions. +- Follow DNN module development best practices. +- Always follow DNN's StyleCop rules for C# and JavaScript. +- Always use StyleCop to add file header license to all C# files. +- Always put using directives inside the namespace, sorted alphabetically and grouped by system and third-party libraries. +- Always put a blank link between system and third-party libraries using directives. +- Use Razor syntax when possible for component-based UI development. +- Async/await should be used where applicable to ensure non-blocking UI operations. +- Classes should always be internal unless they are intended to be public APIs. +- Always add using directives for namespaces that are used in the file, and remove unused using directives. +- Never use 'Active Forums' in code, always use 'DNN Community Forums' instead. + +## Naming Conventions +- Follow PascalCase for component names, method names, and public members. +- Use underscore prefix and then PascalCase for private fields. +- Use camelCase for local variables. +- Prefix interface names with "I" (e.g., IUserService). + +## .NET Specific Guidelines +- Leverage DNN Dependency Injection for services when possible. +- DNN modules should be developed using DNN version 9.11 and compatible with .NET Framework 4.8. +- Always use the latest stable version of .NET libraries and packages compatible with DNN. +- Use NuGet packages for third-party libraries, ensuring they are compatible with DNN and .NET Framework 4.8. +- Use C# compatible with .NET Framework 4.8, avoiding features exclusive to .NET Core or .NET 5+. + +## Error Handling and Validation +- Implement proper error handling for Web API calls. + +## DNN Entities, Controllers, and Services +- Always create and use DNN-style entities (e.g., ForumUserInfo, ForumPostInfo) for data representation. +- Always create and use DNN-style services (e.g., IForumService, IUserService) for business logic. +- Always create controllers in Controllers folder, and use the appropriate namespaces, such as DotNetNuke.Modules.ActiveForums.Controllers. +- Always create controllers that inherit from DotNetNuke.Modules.ActiveForums.Controllers.RepositoryControllerBase for the appropriate entity. +- Always create controllers as internal classes, unless they are intended to be public APIs. +- Always create an entity class for each DNN entity, such as ForumUserInfo, ForumPostInfo, etc., in the Entities folder, and use namespace DotNetNuke.Modules.ActiveForums.Entities. +- Entity classes should use DNN DAL2 PetaPoco standards, and include TableName and PrimaryKey attributes, for example: + [TableName("activeforums_Content")] + [PrimaryKey("ContentId", AutoIncrement = true)] +- Always map entity's DateUpdated and DateCreated properties, ensuring they are stored in UTC format. +- Always add using DotNetNuke.ComponentModel.DataAnnotations; to the entity class files. + +## SQL DataProvider and Database Access +- Use DNN's built-in DataProvider pattern for database access. +- Create SQL scripts for database migrations and updates, ensuring they are compatible with DNN's upgrade process. +- Always use GETUTCDATE() for date and time storage in the database. +- Always add DateCreated and DataModified to all tables. +- Always default DateCreated to GETUTCDATE() and DateUpdated to GETUTCDATE() on insert and update operations. +- Database object names should be in camelCase. +- Always prefix database object names with activeforums_ to avoid conflicts with other modules. +- Always prefix database objects (tables, views, stored procedures) with the {databaseOwner}{objectQualifier} prefixes to ensure compatibility with DNN's multi-tenant architecture. +- Examples : {databaseOwner}[{objectQualifier}activeforums_ForumPosts] for ForumPosts table, IX_{objectQualifier}activeforums_Content_ModuleId for an index. +- Always add an auto-incrementing identity as primary key to all tables, using the naming convention PK_{objectQualifier}activeforums_{TableName} (e.g., PK_activeforums_ForumPosts). +- Always add indexes to frequently queried columns, using the naming convention IX_{objectQualifier}activeforums_{TableName}_{ColumnName} (e.g., IX_activeforums_ForumPosts_ModuleId). +- Always use NOT EXISTS check when creating tables and adding columns ot existing tables. +- Always use EXISTS check with DROP statements when creating indexes. +- Always use parameterized queries to prevent SQL injection attacks. +- Always create a matching entity class when creating a new table, such as ForumUserInfo, ForumPostInfo, etc., in the Entities folder. +- When adding a new SqlDataProvider, always offer to update the DNN manifest file (DnnCommunityForums.dnn) to reference the new version, ensuring it is compatible with DNN's upgrade process. + +## Caching Strategies +- Implement in-memory caching for all Controllers and services to improve performance and reduce database load. +- Caching should use methods in Cache.cs, such as ContentCacheRetrieve, ContentCacheStore, and ContentCacheRemove for content, and SettingsCache, SettingsCacheRetrieve, and SettingsCacheStore for settings. + +## API Design and Integration +- Use HttpClient or other appropriate services to communicate with external APIs or your own backend. +- Implement error handling for API calls using try-catch and provide proper user feedback in the UI. + +## Testing and Debugging in Visual Studio +- Create unit tests for all public and internal methods using NUnit. +- Use Moq for mocking dependencies during tests, leveraging TestBase.cs for shared test setup. +- Create unit tests in DnnCommunityForumsTests project for testing DNN module functionality. + +## Security and Authentication +- All user properties should be accesed using ForumUserInfo and then DNN UserInfo. +- Use HTTPS for all web communication and ensure proper CORS policies are implemented. + +## API Documentation +- Ensure XML documentation for models and API methods for enhancing sufficient documentation. \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.resx index c464989c5..f00c75ff7 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.resx @@ -1858,4 +1858,10 @@ From, Gravatar refreshed for user {0} + + Recycle Bin + + + Restore + \ No newline at end of file diff --git a/Dnn.CommunityForums/Controllers/ReplyController.cs b/Dnn.CommunityForums/Controllers/ReplyController.cs index 39ce6af53..90e9e4dd0 100644 --- a/Dnn.CommunityForums/Controllers/ReplyController.cs +++ b/Dnn.CommunityForums/Controllers/ReplyController.cs @@ -145,6 +145,28 @@ public void Reply_Delete(int portalId, int forumId, int topicId, int replyId, in } } + public void Restore(int portalId, int forumId, int topicId, int replyId) + { + var reply = this.GetById(replyId); + reply.IsDeleted = false; + this.Update(reply); + reply.Content.IsDeleted = false; + new DotNetNuke.Modules.ActiveForums.Controllers.ContentController().Update(reply.Content); + DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_SaveToForum(forumId, topicId, replyId); /* this updates LastReplyId in ForumTopics */ + + DotNetNuke.Modules.ActiveForums.Controllers.ForumController.UpdateForumLastUpdates(forumId); + + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForForum(reply.ModuleId, reply.ForumId); + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForReply(reply.ModuleId, reply.ReplyId); + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForTopic(reply.ModuleId, reply.TopicId); + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForContent(reply.ModuleId, reply.ContentId); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(reply.ModuleId, string.Format(CacheKeys.ForumViewPrefix, reply.ModuleId)); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(reply.ModuleId, string.Format(CacheKeys.TopicViewPrefix, reply.ModuleId)); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(reply.ModuleId, string.Format(CacheKeys.TopicsViewPrefix, reply.ModuleId)); + + Utilities.UpdateModuleLastContentModifiedOnDate(reply.ModuleId); + } + public int Reply_QuickCreate(int portalId, int moduleId, int forumId, int topicId, int replyToId, string subject, string body, int userId, string displayName, bool isApproved, string iPAddress) { int replyId = -1; diff --git a/Dnn.CommunityForums/Controllers/TopicController.cs b/Dnn.CommunityForums/Controllers/TopicController.cs index 7465e8ee9..c479c097d 100644 --- a/Dnn.CommunityForums/Controllers/TopicController.cs +++ b/Dnn.CommunityForums/Controllers/TopicController.cs @@ -292,6 +292,28 @@ public static int Save(DotNetNuke.Modules.ActiveForums.Entities.TopicInfo ti) return Convert.ToInt32(DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_Save(ti.Forum.PortalId, ti.TopicId, ti.ViewCount, ti.ReplyCount, ti.IsLocked, ti.IsPinned, ti.TopicIcon, ti.StatusId, ti.IsApproved, ti.IsDeleted, ti.IsAnnounce, ti.IsArchived, ti.AnnounceStart ?? Utilities.NullDate(), ti.AnnounceEnd ?? Utilities.NullDate(), ti.Content.Subject.Trim(), ti.Content.Body.Trim(), ti.Content.Summary.Trim(), ti.Content.DateCreated, ti.Content.DateUpdated, ti.Content.AuthorId, ti.Content.AuthorName, ti.Content.IPAddress, (int)ti.TopicType, ti.Priority, ti.TopicUrl, ti.TopicData)); } + public void Restore(int portalId, int forumId, int topicId) + { + var topic = this.GetById(topicId); + topic.IsDeleted = false; + this.Update(topic); + topic.Content.IsDeleted = false; + new DotNetNuke.Modules.ActiveForums.Controllers.ContentController().Update(topic.Content); + DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_SaveToForum(forumId, topicId, topic.LastReplyId); + + DotNetNuke.Modules.ActiveForums.Controllers.ForumController.UpdateForumLastUpdates(forumId); + + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForForum(topic.ModuleId, topic.ForumId); + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForReply(topic.ModuleId, topic.ReplyId); + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForTopic(topic.ModuleId, topic.TopicId); + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForContent(topic.ModuleId, topic.ContentId); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(topic.ModuleId, string.Format(CacheKeys.ForumViewPrefix, topic.ModuleId)); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(topic.ModuleId, string.Format(CacheKeys.TopicViewPrefix, topic.ModuleId)); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(topic.ModuleId, string.Format(CacheKeys.TopicsViewPrefix, topic.ModuleId)); + + Utilities.UpdateModuleLastContentModifiedOnDate(topic.ModuleId); + } + public void DeleteById(int topicId) { DotNetNuke.Modules.ActiveForums.Entities.TopicInfo ti = this.GetById(topicId); diff --git a/Dnn.CommunityForums/Services/Controllers/ReplyController.cs b/Dnn.CommunityForums/Services/Controllers/ReplyController.cs index 289c0daf6..cf38419a1 100644 --- a/Dnn.CommunityForums/Services/Controllers/ReplyController.cs +++ b/Dnn.CommunityForums/Services/Controllers/ReplyController.cs @@ -133,6 +133,50 @@ public HttpResponseMessage Delete(int forumId, int replyId) DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); } + return this.Request.CreateResponse(HttpStatusCode.BadRequest); + } +#pragma warning disable CS1570 + /// + /// Reatores a Reply + /// + /// + /// + /// + /// https://dnndev.me/API/ActiveForums/Reply/Restore?forumId=xxx&replyId=zzz +#pragma warning restore CS1570 + [HttpPost] + [DnnAuthorize] + [ForumsAuthorize(SecureActions.Moderate)] + public HttpResponseMessage Restore(ReplyDto dto) + { + try + { + if (dto.ForumId > 0 && dto.ReplyId > 0) + { + var rc = new DotNetNuke.Modules.ActiveForums.Controllers.ReplyController(this.ForumModuleId); + var r = rc.GetById(dto.ReplyId); + if (r != null) + { + if (r.IsDeleted == false) + { + return this.Request.CreateResponse(HttpStatusCode.BadRequest); + } + + rc.Restore(this.ActiveModule.PortalID, + dto.ForumId, + r.TopicId, + dto.ReplyId); + return this.Request.CreateResponse(HttpStatusCode.OK, string.Empty); + } + } + + return this.Request.CreateResponse(HttpStatusCode.NotFound); + } + catch (Exception ex) + { + DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); + } + return this.Request.CreateResponse(HttpStatusCode.BadRequest); } } diff --git a/Dnn.CommunityForums/Services/Controllers/TopicController.cs b/Dnn.CommunityForums/Services/Controllers/TopicController.cs index 11df1714c..d6b5a859b 100644 --- a/Dnn.CommunityForums/Services/Controllers/TopicController.cs +++ b/Dnn.CommunityForums/Services/Controllers/TopicController.cs @@ -565,6 +565,49 @@ public HttpResponseMessage Update(TopicDto2 dto) DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); } + return this.Request.CreateResponse(HttpStatusCode.BadRequest); + } +#pragma warning disable CS1570 + /// + /// Reatores a Topic + /// + /// + /// + /// + /// https://dnndev.me/API/ActiveForums/Topic/Restore?forumId=xxx&topicId=zzz +#pragma warning restore CS1570 + [HttpPost] + [DnnAuthorize] + [ForumsAuthorize(SecureActions.Moderate)] + public HttpResponseMessage Restore(TopicDto1 dto) + { + try + { + if (dto.ForumId > 0 && dto.TopicId > 0) + { + var topicController = new DotNetNuke.Modules.ActiveForums.Controllers.TopicController(this.ForumModuleId); + var topic = topicController.GetById(dto.TopicId); + if (topic != null) + { + if (topic.IsDeleted == false) + { + return this.Request.CreateResponse(HttpStatusCode.BadRequest); + } + + topicController.Restore(this.ActiveModule.PortalID, + dto.ForumId, + dto.TopicId); + return this.Request.CreateResponse(HttpStatusCode.OK, string.Empty); + } + } + + return this.Request.CreateResponse(HttpStatusCode.NotFound); + } + catch (Exception ex) + { + DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); + } + return this.Request.CreateResponse(HttpStatusCode.BadRequest); } } diff --git a/Dnn.CommunityForums/controls/af_recycle_bin.ascx b/Dnn.CommunityForums/controls/af_recycle_bin.ascx new file mode 100644 index 000000000..d34a8bd71 --- /dev/null +++ b/Dnn.CommunityForums/controls/af_recycle_bin.ascx @@ -0,0 +1,34 @@ +<%@ control language="C#" autoeventwireup="false" codebehind="af_recycle_bin.ascx.cs" inherits="DotNetNuke.Modules.ActiveForums.af_recycle_bin" %> +<%@ Register TagPrefix="asp" Namespace="System.Web.UI" Assembly="System.Web" %> +

+ [RESX:RecycleBin] +

+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
diff --git a/Dnn.CommunityForums/controls/af_recycle_bin.ascx.cs b/Dnn.CommunityForums/controls/af_recycle_bin.ascx.cs new file mode 100644 index 000000000..962e49b65 --- /dev/null +++ b/Dnn.CommunityForums/controls/af_recycle_bin.ascx.cs @@ -0,0 +1,162 @@ +// Copyright (c) by DNN Community +// +// DNN Community licenses this file to you under the MIT license. +// +// See the LICENSE file in the project root for more information. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +// to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace DotNetNuke.Modules.ActiveForums +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web.UI; + using System.Web.UI.WebControls; + + using DotNetNuke.Collections; + + public partial class af_recycle_bin : ForumBase + { + protected global::System.Web.UI.WebControls.Label lblRecycleBin; + protected global::System.Web.UI.WebControls.GridView dgrdRestoreView; + + class RestoreData + { + public int ForumId { get; set; } + + public int TopicId { get; set; } + + public int ReplyId { get; set; } + + public bool IsReply { get; set; } + + public string Subject { get; set; } + + public string ForumName { get; set; } + + public string AuthorName { get; set; } + + public DateTime DateCreated { get; set; } + } + + protected override void OnInit(EventArgs e) + { + base.OnInit(e); + + this.lblRecycleBin.Text = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:RecycleBin]"); + this.dgrdRestoreView.Columns[2].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Reply]"); + this.dgrdRestoreView.Columns[3].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Forum]"); + this.dgrdRestoreView.Columns[4].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Subject]"); + this.dgrdRestoreView.Columns[5].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Author]"); + this.dgrdRestoreView.Columns[6].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:DateCreated]"); + + this.dgrdRestoreView.PageIndexChanging += this.RestoreViewGridRowPageIndexChanging; + this.dgrdRestoreView.RowDataBound += this.OnRestoreViewGridRowDataBound; + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + try + { + int _pageSize = this.MainSettings.PageSize; + if (this.UserInfo.UserID > 0) + { + _pageSize = this.UserDefaultPageSize; + } + + if (_pageSize < 5) + { + _pageSize = 10; + } + + this.dgrdRestoreView.PageSize = _pageSize; + + if (this.UserId > 0 && this.ForumUser.GetIsMod(this.ForumModuleId)) + { + this.BindRestoreView(); + } + } + catch (Exception ex) + { + Exceptions.LogException(ex); + } + } + + private void BindRestoreView() + { + this.dgrdRestoreView.DataSource = this.GetData().ToList(); + this.dgrdRestoreView.DataBind(); + } + + + private IEnumerable GetData() + { + var restoreData = new DotNetNuke.Modules.ActiveForums.Controllers.ContentController().Find("WHERE IsDeleted = 1 AND ModuleId = @0", this.ForumModuleId); + var data = restoreData.Select(content => + { + return new RestoreData + { + TopicId = content.Post.TopicId, + ReplyId = content.Post.ReplyId, + IsReply = content.Post.IsReply, + Subject = content.Subject, + ForumName = content.Post.Forum.ForumName, + ForumId = content.Post.ForumId, + AuthorName = content.AuthorName, + DateCreated = content.DateCreated, + }; + }); + + } + + protected void OnRestoreViewGridRowDataBound(object sender, GridViewRowEventArgs e) + { + if (e.Row.RowType == DataControlRowType.DataRow) + { + var restoreData = e.Row.DataItem as RestoreData; + foreach (TableCell cell in e.Row.Cells) + { + foreach (Control cellControl in cell.Controls) + { + if (cellControl is Button) + { + var restoreButton = cellControl as Button; + if (!(restoreButton == null)) + { + restoreButton.Enabled = true; + if (restoreData.IsReply) + { + restoreButton.Attributes.Add("onclick", $"amaf_restoreReply({this.ForumModuleId},{restoreData.ForumId},{restoreData.ReplyId});"); + } + else + { + restoreButton.Attributes.Add("onclick", $"amaf_restoreTopic({this.ForumModuleId},{restoreData.ForumId},{restoreData.TopicId});"); + } + } + } + } + } + } + } + + protected void RestoreViewGridRowPageIndexChanging(object sender, GridViewPageEventArgs e) + { + this.dgrdRestoreView.PageIndex = e.NewPageIndex; + this.dgrdRestoreView.DataBind(); + } + } +} diff --git a/Dnn.CommunityForums/scripts/afcommon.js b/Dnn.CommunityForums/scripts/afcommon.js index f3694f651..a4aa4d45f 100644 --- a/Dnn.CommunityForums/scripts/afcommon.js +++ b/Dnn.CommunityForums/scripts/afcommon.js @@ -267,7 +267,33 @@ function amaf_topicDel(mid, fid, tid) { }); }; }; - +function amaf_topicRestore(mid, fid, tid) { + if (confirm(amaf.resx.DeleteConfirm)) { + var sf = $.ServicesFramework(mid); + $.ajax({ + type: "POST", + url: dnn.getVar("sf_siteRoot", "/") + 'API/ActiveForums/Topic/Restore?forumId=' + fid + '&topicId=' + tid, + beforeSend: sf.setModuleHeaders + }).done(function (data) { + afreload(); + }).fail(function (xhr, status) { + alert('error restoring topic'); + }); + }; +};function amaf_replyRestore(mid, fid, tid) { + if (confirm(amaf.resx.DeleteConfirm)) { + var sf = $.ServicesFramework(mid); + $.ajax({ + type: "POST", + url: dnn.getVar("sf_siteRoot", "/") + 'API/ActiveForums/Reply/Restore?forumId=' + fid + '&replyId=' + tid, + beforeSend: sf.setModuleHeaders + }).done(function (data) { + afreload(); + }).fail(function (xhr, status) { + alert('error restoring topic'); + }); + }; +}; function amaf_likePost(mid, fid, cid) { var sf = $.ServicesFramework(mid); var params = { diff --git a/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider index 9f062203a..eeba783bf 100644 --- a/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider +++ b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider @@ -749,6 +749,7 @@ DROP INDEX [IX_{objectQualifier}activeforums_Content_IsDeleted] ON {databaseOwne GO CREATE NONCLUSTERED INDEX [IX_{objectQualifier}activeforums_Content_IsDeleted] ON {databaseOwner}{objectQualifier}activeforums_Content ( + [ModuleId] ASC, [ContentId] ASC ) WHERE IsDeleted = 1 @@ -784,4 +785,6 @@ GO /* issues 1406 - end - indexes on IsDeleted columns in activeforums_Content, activeforums_Topics and activeforums_Replies tables */ -/* ---------------------- */ \ No newline at end of file + +/* ---------------------- */ + From 58b3decd82a90ea242eafdca122a5d1679d14f8a Mon Sep 17 00:00:00 2001 From: John Henley Date: Wed, 23 Jul 2025 21:54:35 +0000 Subject: [PATCH 14/40] TASK: Add recycle bin to toolbar --- .../SharedResources.de-DE.resx | 9 ++ .../SharedResources.es-ES.resx | 9 ++ .../SharedResources.fr-FR.resx | 9 ++ .../SharedResources.it-IT.resx | 9 ++ .../SharedResources.nl-NL.resx | 9 ++ .../App_LocalResources/SharedResources.resx | 3 + Dnn.CommunityForums/Classic.ascx.cs | 8 ++ .../Controllers/ForumUserController.cs | 2 +- .../Controllers/ReplyController.cs | 4 +- .../Controllers/TopicController.cs | 4 +- Dnn.CommunityForums/Enums/DeleteBehavior.cs | 28 +++++ .../Services/AdminServiceController.cs | 2 +- .../Tokens/ForumsModuleTokenReplacer.cs | 4 + Dnn.CommunityForums/class/Globals.cs | 2 + Dnn.CommunityForums/class/Settings.cs | 11 +- .../components/Extensions/ReWriter.cs | 3 + .../components/Replies/Replies.cs | 2 +- .../components/Topics/TopicsController.cs | 2 +- .../config/templates/ToolBar.ascx | 1 + .../admin_manageforums_forumeditor.ascx.cs | 2 +- .../controls/af_modtopics.ascx.cs | 4 +- .../controls/af_recycle_bin.ascx | 13 +- .../controls/af_recycle_bin.ascx.cs | 17 ++- Dnn.CommunityForums/scripts/afcommon.js | 28 ++++- Dnn.CommunityForums/scripts/resx.js | 1 + .../sql/09.01.00.SqlDataProvider | 113 ++++++++++++++++++ 26 files changed, 269 insertions(+), 30 deletions(-) create mode 100644 Dnn.CommunityForums/Enums/DeleteBehavior.cs diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx index 56183f964..eaa3966fc 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx @@ -1859,4 +1859,13 @@ Von Gravatar für Benutzer-{0} aktualisiert + + Papierkorb + + + Wiederherstellen + + + Sind Sie sicher, dass Sie diesen Artikel wiederherstellen möchten? + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx index 32e76ce6f..8a01f8829 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx @@ -1858,4 +1858,13 @@ De Gravatar actualizado para el {0} de usuario + + Papelera de reciclaje + + + Restaurar + + + ¿Está seguro de que desea restaurar este artículo? + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx index 701d12cd7..2887311fa 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx @@ -1855,4 +1855,13 @@ De, Gravatar actualisé pour le {0} utilisateur + + Corbeille + + + Restaurer + + + Êtes-vous sûr de vouloir restaurer cet article ? + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx index 62a8b7089..385046001 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx @@ -1858,4 +1858,13 @@ Da Gravatar aggiornato per l'utente {0} + + Cestino + + + Restaurare + + + Sei sicuro di voler ripristinare questo oggetto? + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx index 37bda6e6a..4ac7dc9ff 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx @@ -1894,4 +1894,13 @@ Van, Gravatar vernieuwd voor {0} gebruiker + + Prullenbak + + + Herstellen + + + Weet je zeker dat je dit item wilt restaureren? + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.resx index f00c75ff7..e06668b69 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.resx @@ -1864,4 +1864,7 @@ From, Restore + + Are you sure you wish to restore this item? + \ No newline at end of file diff --git a/Dnn.CommunityForums/Classic.ascx.cs b/Dnn.CommunityForums/Classic.ascx.cs index c55d2eef7..42ae8199f 100644 --- a/Dnn.CommunityForums/Classic.ascx.cs +++ b/Dnn.CommunityForums/Classic.ascx.cs @@ -90,6 +90,10 @@ protected override void OnLoad(EventArgs e) { ctl = Views.MySubscriptions; } + else if (this.Request.Params[ParamKeys.ViewType] != null && this.Request.Params[ParamKeys.ViewType] == Views.Grid && this.Request.Params[ParamKeys.GridType] != null && this.Request.Params[ParamKeys.GridType] == Views.RecycleBin) + { + ctl = Views.RecycleBin; + } else if (this.Request.Params[ParamKeys.ViewType] != null && this.Request.Params[ParamKeys.ViewType] == Views.Grid && this.Request.Params[ParamKeys.GridType] != null && this.Request.Params[ParamKeys.GridType] == Views.Likes) { ctl = Views.Likes; @@ -174,6 +178,10 @@ private void GetControl(string view, string options) { ctl = (ForumBase)this.LoadControl(this.Page.ResolveUrl(Globals.ModulePath + "controls/profile_mysubscriptions.ascx")); } + else if (view.ToUpperInvariant() == Views.RecycleBin.ToUpperInvariant() && this.Request.IsAuthenticated) + { + ctl = (ForumBase)this.LoadControl(this.Page.ResolveUrl(Globals.ModulePath + "controls/af_recycle_bin.ascx")); + } else if (view.ToUpperInvariant() == "FORUMVIEW") { ctl = (ForumBase)new DotNetNuke.Modules.ActiveForums.Controls.ForumView(); diff --git a/Dnn.CommunityForums/Controllers/ForumUserController.cs b/Dnn.CommunityForums/Controllers/ForumUserController.cs index 7d7984315..c6cab0db9 100644 --- a/Dnn.CommunityForums/Controllers/ForumUserController.cs +++ b/Dnn.CommunityForums/Controllers/ForumUserController.cs @@ -311,7 +311,7 @@ internal static void BanUser(int portalId, int tabId, int moduleId, string modul log.AddProperty("Message", userBannedMsg); DotNetNuke.Services.Log.EventLog.LogController.Instance.AddLog(log); - DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_Delete_For_User(moduleId: moduleId, userId: authorId, delBehavior: SettingsBase.GetModuleSettings(moduleId).DeleteBehavior); + DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_Delete_For_User(moduleId: moduleId, userId: authorId, delBehavior: (int)SettingsBase.GetModuleSettings(moduleId).DeleteBehavior); if (bannedUser != null) { diff --git a/Dnn.CommunityForums/Controllers/ReplyController.cs b/Dnn.CommunityForums/Controllers/ReplyController.cs index 90e9e4dd0..e8441ee2b 100644 --- a/Dnn.CommunityForums/Controllers/ReplyController.cs +++ b/Dnn.CommunityForums/Controllers/ReplyController.cs @@ -101,10 +101,10 @@ public DotNetNuke.Modules.ActiveForums.Entities.ReplyInfo GetByContentId(int con return ri; } - public void Reply_Delete(int portalId, int forumId, int topicId, int replyId, int delBehavior) + public void Reply_Delete(int portalId, int forumId, int topicId, int replyId, DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior delBehavior) { var ri = this.GetById(replyId); - DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Reply_Delete(forumId, topicId, replyId, delBehavior); + DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Reply_Delete(forumId, topicId, replyId, (int)delBehavior); DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_SaveToForum(forumId, topicId, replyId); /* this updates LastReplyId in ForumTopics */ DotNetNuke.Modules.ActiveForums.Controllers.ForumController.UpdateForumLastUpdates(forumId); diff --git a/Dnn.CommunityForums/Controllers/TopicController.cs b/Dnn.CommunityForums/Controllers/TopicController.cs index c479c097d..674f36a6e 100644 --- a/Dnn.CommunityForums/Controllers/TopicController.cs +++ b/Dnn.CommunityForums/Controllers/TopicController.cs @@ -321,7 +321,7 @@ public void DeleteById(int topicId) { new Social().DeleteJournalItemForPost(ti.PortalId, ti.ForumId, topicId, 0); - DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_Delete(ti.ForumId, topicId, SettingsBase.GetModuleSettings(ti.ModuleId).DeleteBehavior ); + DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_Delete(ti.ForumId, topicId, (int)SettingsBase.GetModuleSettings(ti.ModuleId).DeleteBehavior ); Utilities.UpdateModuleLastContentModifiedOnDate(ti.ModuleId); DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForForum(ti.ModuleId, ti.ForumId); DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForTopic(ti.ModuleId, ti.TopicId); @@ -330,7 +330,7 @@ public void DeleteById(int topicId) DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(ti.ModuleId, string.Format(CacheKeys.TopicViewPrefix, ti.ModuleId)); DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(ti.ModuleId, string.Format(CacheKeys.TopicsViewPrefix, ti.ModuleId)); - if (SettingsBase.GetModuleSettings(ti.ModuleId).DeleteBehavior != 0) + if (SettingsBase.GetModuleSettings(ti.ModuleId).DeleteBehavior != DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior.Remove) { return; } diff --git a/Dnn.CommunityForums/Enums/DeleteBehavior.cs b/Dnn.CommunityForums/Enums/DeleteBehavior.cs new file mode 100644 index 000000000..e5be4473a --- /dev/null +++ b/Dnn.CommunityForums/Enums/DeleteBehavior.cs @@ -0,0 +1,28 @@ +// Copyright (c) by DNN Community +// +// DNN Community licenses this file to you under the MIT license. +// +// See the LICENSE file in the project root for more information. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +// to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace DotNetNuke.Modules.ActiveForums.Enums +{ + public enum DeleteBehavior : byte + { + Remove = 0, + Recycle = 1, + } +} diff --git a/Dnn.CommunityForums/Services/AdminServiceController.cs b/Dnn.CommunityForums/Services/AdminServiceController.cs index 0941fb30a..f2b8244d0 100644 --- a/Dnn.CommunityForums/Services/AdminServiceController.cs +++ b/Dnn.CommunityForums/Services/AdminServiceController.cs @@ -73,7 +73,7 @@ public class RunMaintenanceDTO public HttpResponseMessage RunMaintenance(RunMaintenanceDTO dto) { var moduleSettings = new SettingsInfo { ModuleId = dto.ModuleId, MainSettings = DotNetNuke.Entities.Modules.ModuleController.Instance.GetModule(moduleId: dto.ModuleId, DotNetNuke.Common.Utilities.Null.NullInteger, true).ModuleSettings }; - var rows = DataProvider.Instance().Forum_Maintenance(dto.ForumId, dto.OlderThan, dto.LastActive, dto.ByUserId, dto.WithNoReplies, dto.DryRun, moduleSettings.DeleteBehavior); + var rows = DataProvider.Instance().Forum_Maintenance(dto.ForumId, dto.OlderThan, dto.LastActive, dto.ByUserId, dto.WithNoReplies, dto.DryRun, (int)moduleSettings.DeleteBehavior); if (dto.DryRun) { return this.Request.CreateResponse(HttpStatusCode.OK, new { Result = string.Format(Utilities.GetSharedResource("[RESX:Maint:DryRunResults]", true), rows.ToString()) }); diff --git a/Dnn.CommunityForums/Services/Tokens/ForumsModuleTokenReplacer.cs b/Dnn.CommunityForums/Services/Tokens/ForumsModuleTokenReplacer.cs index 57b3dee0f..31d3724cc 100644 --- a/Dnn.CommunityForums/Services/Tokens/ForumsModuleTokenReplacer.cs +++ b/Dnn.CommunityForums/Services/Tokens/ForumsModuleTokenReplacer.cs @@ -180,6 +180,10 @@ public string GetProperty(string propertyName, string format, System.Globalizati return PropertyAccess.FormatString(new ControlUtils().BuildUrl(this.PortalSettings.PortalId, this.TabId, this.ModuleId, string.Empty, string.Empty, -1, -1, -1, -1, GridTypes.MostLiked, 1, -1, -1), format); case "toolbar-mostreplies-onclick": return PropertyAccess.FormatString(new ControlUtils().BuildUrl(this.PortalSettings.PortalId, this.TabId, this.ModuleId, string.Empty, string.Empty, -1, -1, -1, -1, GridTypes.MostReplies, 1, -1, -1), format); + case "toolbar-recyclebin-onclick": + return SettingsBase.GetModuleSettings(this.ForumModuleId).DeleteBehavior.Equals(DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior.Recycle) && (accessingUser.IsSuperUser || accessingUser.IsAdmin || new DotNetNuke.Modules.ActiveForums.Controllers.ForumUserController(this.ModuleId).GetByUserId(portalId: accessingUser.PortalID, userId: accessingUser.UserID).GetIsMod(this.ModuleId)) ? + PropertyAccess.FormatString(Utilities.NavigateURL(this.TabId, string.Empty, $"{ParamKeys.ViewType}={Views.RecycleBin}"), format) : + string.Empty; } } catch (Exception ex) diff --git a/Dnn.CommunityForums/class/Globals.cs b/Dnn.CommunityForums/class/Globals.cs index 3464c142c..6c810782f 100644 --- a/Dnn.CommunityForums/class/Globals.cs +++ b/Dnn.CommunityForums/class/Globals.cs @@ -428,6 +428,7 @@ public class Views public const string ModerateBan = "modban"; public const string ModerateReport = "modreport"; public const string Likes = "likes"; + public const string RecycleBin = "recyclebin"; } internal static class GridTypes @@ -443,6 +444,7 @@ internal static class GridTypes public const string Unresolved = "unresolved"; public const string Announcements = "announcements"; public const string Tags = "tags"; + public const string RecycleBin = "recyclebin"; } public class PostActions diff --git a/Dnn.CommunityForums/class/Settings.cs b/Dnn.CommunityForums/class/Settings.cs index 24a6b3c46..33dc1ff6e 100644 --- a/Dnn.CommunityForums/class/Settings.cs +++ b/Dnn.CommunityForums/class/Settings.cs @@ -215,9 +215,16 @@ public int EditInterval get { return this.MainSettings.GetInt(SettingKeys.EditInterval); } } - public int DeleteBehavior + public DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior DeleteBehavior { - get { return this.MainSettings.GetInt(SettingKeys.DeleteBehavior); } + get + { + DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior parsedValue; + + return Enum.TryParse(this.MainSettings.GetString(SettingKeys.DeleteBehavior), true, out parsedValue) + ? parsedValue + : DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior.Recycle; + } } public ProfileVisibilities ProfileVisibility diff --git a/Dnn.CommunityForums/components/Extensions/ReWriter.cs b/Dnn.CommunityForums/components/Extensions/ReWriter.cs index 366bb8315..4893b71a5 100644 --- a/Dnn.CommunityForums/components/Extensions/ReWriter.cs +++ b/Dnn.CommunityForums/components/Extensions/ReWriter.cs @@ -491,6 +491,9 @@ public void OnBeginRequest(object s, EventArgs e) case 10: v = GridTypes.Unresolved; break; + case 13: + v = GridTypes.RecycleBin; + break; } sendTo = ResolveUrl(app.Context.Request.ApplicationPath, "~/default.aspx?tabid=" + this.tabId + $"&{ParamKeys.ViewType}={Views.Grid}&{ParamKeys.GridType}=" + v + sPage + qs); diff --git a/Dnn.CommunityForums/components/Replies/Replies.cs b/Dnn.CommunityForums/components/Replies/Replies.cs index bd43ad950..562acbf08 100644 --- a/Dnn.CommunityForums/components/Replies/Replies.cs +++ b/Dnn.CommunityForums/components/Replies/Replies.cs @@ -29,7 +29,7 @@ public class ReplyInfo : DotNetNuke.Modules.ActiveForums.Entities.ReplyInfo { } public class ReplyController { [Obsolete("Deprecated in Community Forums. Removing in 10.00.00. Use DotNetNuke.Modules.ActiveForums.Controllers.ReplyController()")] - public void Reply_Delete(int portalId, int forumId, int topicId, int replyId, int delBehavior) => new DotNetNuke.Modules.ActiveForums.Controllers.ReplyController(-1).Reply_Delete(portalId, forumId, topicId, replyId, delBehavior); + public void Reply_Delete(int portalId, int forumId, int topicId, int replyId, int delBehavior) => new DotNetNuke.Modules.ActiveForums.Controllers.ReplyController(-1).Reply_Delete(portalId, forumId, topicId, replyId, (DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior)delBehavior); [Obsolete("Deprecated in Community Forums. Removing in 10.00.00. Use DotNetNuke.Modules.ActiveForums.Controllers.ReplyController()")] public int Reply_QuickCreate(int portalId, int moduleId, int forumId, int topicId, int replyToId, string subject, string body, int userId, string displayName, bool isApproved, string iPAddress) => new DotNetNuke.Modules.ActiveForums.Controllers.ReplyController(moduleId).Reply_QuickCreate(portalId, moduleId, forumId, topicId, replyToId, subject, body, userId, displayName, isApproved, iPAddress); diff --git a/Dnn.CommunityForums/components/Topics/TopicsController.cs b/Dnn.CommunityForums/components/Topics/TopicsController.cs index b7f8ac5a4..af0238403 100644 --- a/Dnn.CommunityForums/components/Topics/TopicsController.cs +++ b/Dnn.CommunityForums/components/Topics/TopicsController.cs @@ -89,7 +89,7 @@ public override IList GetModifiedSearchDocuments(ModuleInfo modu note that this "internals" method is suggested by blog post (https://www.dnnsoftware.com/community-blog/cid/154913/integrating-with-search-introducing-modulesearchbase#Comment106) and also is used by the Community Links module (https://github.com/DNNCommunity/DNN.Links/blob/development/Components/FeatureController.cs) */ - if (ms.DeleteBehavior != 1) + if (ms.DeleteBehavior != DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior.Recycle) { DotNetNuke.Services.Search.Internals.InternalSearchController.Instance.DeleteSearchDocumentsByModule(moduleInfo.PortalID, moduleInfo.ModuleID, moduleInfo.ModuleDefID); beginDateUtc = SqlDateTime.MinValue.Value.AddDays(1); diff --git a/Dnn.CommunityForums/config/templates/ToolBar.ascx b/Dnn.CommunityForums/config/templates/ToolBar.ascx index f4a1e126d..2af034e71 100644 --- a/Dnn.CommunityForums/config/templates/ToolBar.ascx +++ b/Dnn.CommunityForums/config/templates/ToolBar.ascx @@ -22,6 +22,7 @@
  • [DCF:TOOLBAR-MODERATE-ONCLICK|[RESX:Moderate]]
  • +
  • [DCF:TOOLBAR-RECYCLEBIN-ONCLICK|[RESX:RecycleBin]]
  • [DCF:TOOLBAR-MYSETTINGS-ONCLICK|[RESX:MySettings]]
  • [DCF:TOOLBAR-MYSUBSCRIPTIONS-ONCLICK|[RESX:MySubscriptions]]
  • [DCF:TOOLBAR-CONTROLPANEL-ONCLICK|[RESX:ControlPanel]]
  • diff --git a/Dnn.CommunityForums/controls/admin_manageforums_forumeditor.ascx.cs b/Dnn.CommunityForums/controls/admin_manageforums_forumeditor.ascx.cs index 8e3b9090b..18a2a6283 100644 --- a/Dnn.CommunityForums/controls/admin_manageforums_forumeditor.ascx.cs +++ b/Dnn.CommunityForums/controls/admin_manageforums_forumeditor.ascx.cs @@ -275,7 +275,7 @@ protected override void OnLoad(EventArgs e) this.txtReplyOlderThan.Attributes.Add("onkeypress", "return onlyNumbers(event)"); this.txtUserId.Attributes.Add("onkeypress", "return onlyNumbers(event)"); - if (this.MainSettings.DeleteBehavior == 1) + if (this.MainSettings.DeleteBehavior == DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior.Recycle) { this.lblMaintWarn.Text = string.Format(this.GetSharedResource("[RESX:MaintenanceWarning]"), this.GetSharedResource("[RESX:MaintenanceWarning:Recycle]"), this.GetSharedResource("[RESX:MaintenanceWarning:Recycle:Desc]")); } diff --git a/Dnn.CommunityForums/controls/af_modtopics.ascx.cs b/Dnn.CommunityForums/controls/af_modtopics.ascx.cs index 0476ef67e..1e491c8eb 100644 --- a/Dnn.CommunityForums/controls/af_modtopics.ascx.cs +++ b/Dnn.CommunityForums/controls/af_modtopics.ascx.cs @@ -90,7 +90,7 @@ private void cbMod_Callback(object sender, Modules.ActiveForums.Controls.CallBac { if (this.bModDelete) { - int delAction = ms.DeleteBehavior; + int delAction = (int)ms.DeleteBehavior; int tmpForumId = Convert.ToInt32(e.Parameters[1]); int tmpTopicId = Convert.ToInt32(e.Parameters[2]); int tmpReplyId = Convert.ToInt32(e.Parameters[3]); @@ -124,7 +124,7 @@ private void cbMod_Callback(object sender, Modules.ActiveForums.Controls.CallBac DotNetNuke.Modules.ActiveForums.Entities.ReplyInfo ri = new DotNetNuke.Modules.ActiveForums.Controllers.ReplyController(this.ForumModuleId).GetById(tmpReplyId); if (ri != null) { - new DotNetNuke.Modules.ActiveForums.Controllers.ReplyController(this.ForumModuleId).Reply_Delete(this.PortalId, tmpForumId, tmpTopicId, tmpReplyId, delAction); + new DotNetNuke.Modules.ActiveForums.Controllers.ReplyController(this.ForumModuleId).Reply_Delete(this.PortalId, tmpForumId, tmpTopicId, tmpReplyId, (DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior)delAction); if (fi.FeatureSettings.ModDeleteNotify && ri?.Author?.AuthorId > 0) { try diff --git a/Dnn.CommunityForums/controls/af_recycle_bin.ascx b/Dnn.CommunityForums/controls/af_recycle_bin.ascx index d34a8bd71..262d63ee6 100644 --- a/Dnn.CommunityForums/controls/af_recycle_bin.ascx +++ b/Dnn.CommunityForums/controls/af_recycle_bin.ascx @@ -16,10 +16,12 @@ - + - + @@ -32,3 +34,10 @@
+ diff --git a/Dnn.CommunityForums/controls/af_recycle_bin.ascx.cs b/Dnn.CommunityForums/controls/af_recycle_bin.ascx.cs index 962e49b65..69e92f560 100644 --- a/Dnn.CommunityForums/controls/af_recycle_bin.ascx.cs +++ b/Dnn.CommunityForums/controls/af_recycle_bin.ascx.cs @@ -57,11 +57,11 @@ protected override void OnInit(EventArgs e) base.OnInit(e); this.lblRecycleBin.Text = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:RecycleBin]"); - this.dgrdRestoreView.Columns[2].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Reply]"); - this.dgrdRestoreView.Columns[3].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Forum]"); - this.dgrdRestoreView.Columns[4].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Subject]"); - this.dgrdRestoreView.Columns[5].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Author]"); - this.dgrdRestoreView.Columns[6].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:DateCreated]"); + this.dgrdRestoreView.Columns[3].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Reply]"); + this.dgrdRestoreView.Columns[4].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Forum]"); + this.dgrdRestoreView.Columns[5].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Subject]"); + this.dgrdRestoreView.Columns[6].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:Author]"); + this.dgrdRestoreView.Columns[7].HeaderText = DotNetNuke.Modules.ActiveForums.Utilities.GetSharedResource("[RESX:DateCreated]"); this.dgrdRestoreView.PageIndexChanging += this.RestoreViewGridRowPageIndexChanging; this.dgrdRestoreView.RowDataBound += this.OnRestoreViewGridRowDataBound; @@ -106,7 +106,7 @@ private void BindRestoreView() private IEnumerable GetData() { var restoreData = new DotNetNuke.Modules.ActiveForums.Controllers.ContentController().Find("WHERE IsDeleted = 1 AND ModuleId = @0", this.ForumModuleId); - var data = restoreData.Select(content => + return restoreData.OrderByDescending(content => content.DateUpdated).Select(content => { return new RestoreData { @@ -120,7 +120,6 @@ private IEnumerable GetData() DateCreated = content.DateCreated, }; }); - } protected void OnRestoreViewGridRowDataBound(object sender, GridViewRowEventArgs e) @@ -140,11 +139,11 @@ protected void OnRestoreViewGridRowDataBound(object sender, GridViewRowEventArgs restoreButton.Enabled = true; if (restoreData.IsReply) { - restoreButton.Attributes.Add("onclick", $"amaf_restoreReply({this.ForumModuleId},{restoreData.ForumId},{restoreData.ReplyId});"); + restoreButton.Attributes.Add("onclick", $"amaf_replyRestore({this.ForumModuleId},{restoreData.ForumId},{restoreData.TopicId},{restoreData.ReplyId});return RemoveRow(this)"); } else { - restoreButton.Attributes.Add("onclick", $"amaf_restoreTopic({this.ForumModuleId},{restoreData.ForumId},{restoreData.TopicId});"); + restoreButton.Attributes.Add("onclick", $"amaf_topicRestore({this.ForumModuleId},{restoreData.ForumId},{restoreData.TopicId});return RemoveRow(this)"); } } } diff --git a/Dnn.CommunityForums/scripts/afcommon.js b/Dnn.CommunityForums/scripts/afcommon.js index a4aa4d45f..8fd61c867 100644 --- a/Dnn.CommunityForums/scripts/afcommon.js +++ b/Dnn.CommunityForums/scripts/afcommon.js @@ -268,11 +268,18 @@ function amaf_topicDel(mid, fid, tid) { }; }; function amaf_topicRestore(mid, fid, tid) { - if (confirm(amaf.resx.DeleteConfirm)) { + if (confirm(amaf.resx.RestoreConfirm)) { var sf = $.ServicesFramework(mid); + var params = { + forumId: fid, + topicId: tid + }; $.ajax({ type: "POST", - url: dnn.getVar("sf_siteRoot", "/") + 'API/ActiveForums/Topic/Restore?forumId=' + fid + '&topicId=' + tid, + data: JSON.stringify(params), + contentType: "application/json", + dataType: "json", + url: dnn.getVar("sf_siteRoot", "/") + 'API/ActiveForums/Topic/Restore', beforeSend: sf.setModuleHeaders }).done(function (data) { afreload(); @@ -280,17 +287,26 @@ function amaf_topicRestore(mid, fid, tid) { alert('error restoring topic'); }); }; -};function amaf_replyRestore(mid, fid, tid) { - if (confirm(amaf.resx.DeleteConfirm)) { +}; +function amaf_replyRestore(mid, fid, tid, rid) { + if (confirm(amaf.resx.RestoreConfirm)) { var sf = $.ServicesFramework(mid); + var params = { + forumId: fid, + topicId: tid, + replyId: rid + }; $.ajax({ type: "POST", - url: dnn.getVar("sf_siteRoot", "/") + 'API/ActiveForums/Reply/Restore?forumId=' + fid + '&replyId=' + tid, + data: JSON.stringify(params), + contentType: "application/json", + dataType: "json", + url: dnn.getVar("sf_siteRoot", "/") + 'API/ActiveForums/Reply/Restore', beforeSend: sf.setModuleHeaders }).done(function (data) { afreload(); }).fail(function (xhr, status) { - alert('error restoring topic'); + alert('error restoring reply'); }); }; }; diff --git a/Dnn.CommunityForums/scripts/resx.js b/Dnn.CommunityForums/scripts/resx.js index 200b31a6a..98182d7a0 100644 --- a/Dnn.CommunityForums/scripts/resx.js +++ b/Dnn.CommunityForums/scripts/resx.js @@ -14,6 +14,7 @@ amaf.resx = { Pin: '[RESX:Pin]', UnPin: '[RESX:UnPin]', DeleteConfirm: '[RESX:Confirm:Delete]', + RestoreConfirm: '[RESX:Confirm:Restore]', PinConfirm: '[RESX:Confirm:Pin]', UnPinConfirm: '[RESX:Confirm:UnPin]', LockConfirm: '[RESX:Confirm:Lock]', diff --git a/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider index eeba783bf..f7ac51be4 100644 --- a/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider +++ b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider @@ -785,6 +785,119 @@ GO /* issues 1406 - end - indexes on IsDeleted columns in activeforums_Content, activeforums_Topics and activeforums_Replies tables */ +/* issue 1409 - begin - add recycle bin to toolbar */ +/* activeforums_URL_Search needs to be updated to add recycle bin as view */ + +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_URL_Search]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_URL_Search] +GO + +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_URL_Search] +@PortalId int, +@Url nvarchar(max) +AS +DECLARE @views TABLE(id int,viewname nvarchar(50)) +INSERT INTO @views (id,viewname) VALUES (1,'unanswered'); +INSERT INTO @views (id,viewname) VALUES (2,'notread'); +INSERT INTO @views (id,viewname) VALUES (3,'mytopics'); +INSERT INTO @views (id,viewname) VALUES (4,'activetopics'); +INSERT INTO @views (id,viewname) VALUES (5,'afprofile'); +INSERT INTO @views (id,viewname) VALUES (6,'mostliked'); +INSERT INTO @views (id,viewname) VALUES (7,'mostreplies'); +INSERT INTO @views (id,viewname) VALUES (8,'afsubscriptions'); +INSERT INTO @views (id,viewname) VALUES (9,'announcements'); +INSERT INTO @views (id,viewname) VALUES (10,'unresolved'); +INSERT INTO @views (id,viewname) VALUES (13,'recyclebin'); +SELECT TabId, ModuleID, ForumGroupId, ForumId, TopicId, Url,Archived,OtherId,UrlType FROM + ( + SELECT tb.TabID,m.ModuleId, g.ForumGroupId,f.ForumId,t.TopicId, + (CASE WHEN s.SettingValue <> '' THEN s.SettingValue + '/' Else '' END) + g.PrefixURL + '/' + f.PrefixURL + '/' + t.URL + '/' as URL, 0 as Archived,-1 as OtherId,0 as URLType from {databaseOwner}[{objectQualifier}activeforums_Topics] as t + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_ForumTopics] as ft ON ft.TopicId = t.TopicId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Forums] as f ON f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Groups] as g ON g.ForumGroupId = f.ForumGroupId + INNER JOIN {databaseOwner}[{objectQualifier}ModuleSettings] as s ON s.ModuleId = f.ModuleId AND s.SettingName = 'URLBASE' + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = f.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE tb.PortalID = @PortalId AND ISNULL(t.URL,'') <> '' AND ISNULL(f.PrefixURL,'') <> '' + UNION + SELECT tb.TabID,m.ModuleId,g.ForumGroupId,f.ForumId,-1, + (CASE WHEN s.SettingValue <> '' THEN s.SettingValue + '/' Else '' END) + g.PrefixURL + '/' + f.PrefixURL + '/' as URL, 0 as Archived,-1,0 FROM + {databaseOwner}[{objectQualifier}activeforums_Forums] as f + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Groups] as g ON g.ForumGroupId = f.ForumGroupId + INNER JOIN {databaseOwner}[{objectQualifier}ModuleSettings] as s ON s.ModuleId = f.ModuleId AND s.SettingName = 'URLBASE' + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = f.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE tb.PortalID = @PortalId AND ISNULL(f.PrefixURL,'') <> '' + UNION + SELECT tb.TabID,m.ModuleId,g.ForumGroupId,-1,-1, + (CASE WHEN s.SettingValue <> '' THEN s.SettingValue + '/' Else '' END) + g.PrefixURL + '/' as URL, 0 as Archived,-1,0 FROM + {databaseOwner}[{objectQualifier}activeforums_Groups] as g + INNER JOIN {databaseOwner}[{objectQualifier}ModuleSettings] as s ON s.ModuleId = g.ModuleId AND s.SettingName = 'URLBASE' + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = g.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE tb.PortalID = @PortalId AND ISNULL(g.PrefixURL,'') <> '' + UNION + SELECT tb.TabID,m.ModuleId,-1,-1,-1, + (CASE WHEN s.SettingValue <> '' THEN s.SettingValue + '/' Else '' END) as URL, 0 as Archived,-1,0 FROM + {databaseOwner}[{objectQualifier}ModuleSettings] as s + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = s.ModuleId AND s.SettingName = 'URLBASE' + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE tb.PortalID = @PortalId AND s.SettingValue <> '' + UNION + SELECT m.TabID,m.ModuleID,u.ForumGroupId,u.ForumId,u.TopicId, u.URL, 1 as Archived,-1,0 from {databaseOwner}[{objectQualifier}activeforums_URL] as u + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Groups] as g ON u.ForumGroupId = g.ForumGroupId + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = g.ModuleId + WHERE u.PortalId = @PortalId + UNION + SELECT TabId, ModuleId,-1 as ForumGroupId,-1 as ForumId,-1 as TopicId, + (CASE WHEN UrlBase <> '' THEN UrlBase + '/' Else '' END) + UrlOther + '/' + v.viewname + '/' as URL,0 as Archived,v.id,1 from ( + SELECT m.TabId, ss.ModuleId, SettingValue,SettingName FROM {databaseOwner}[{objectQualifier}ModuleSettings] as ss + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = ss.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE (SettingName = 'URLBASE' OR SettingName = 'URLOTHER') AND tb.PortalID = @PortalId + ) as s + PIVOT (MAX(SettingValue) for SettingName in (urlbase,UrlOther)) as pu,@views as v + UNION + SELECT TabId, pu.ModuleId,-1 as ForumGroupId,-1 as ForumId,-1 as TopicId, + (CASE WHEN UrlBase <> '' THEN UrlBase + '/' Else '' END) + URLCATS + '/' + REPLACE(LOWER(t.TagName),' ','-') + '/' as URL,0 as Archived,t.TagId,2 from ( + SELECT m.TabId, ss.ModuleId, SettingValue,SettingName FROM {databaseOwner}[{objectQualifier}ModuleSettings] as ss + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = ss.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE (SettingName = 'URLBASE' OR SettingName = 'URLCATS') AND tb.PortalID = @PortalId + ) as s + PIVOT (MAX(SettingValue) for SettingName in (urlbase,URLCATS)) as pu + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Tags] as t ON t.ModuleId = pu.ModuleId AND t.IsCategory = 1 + UNION + SELECT TabId, pu.ModuleId,-1 as ForumGroupId,-1 as ForumId,-1 as TopicId, + (CASE WHEN UrlBase <> '' THEN UrlBase + '/' Else '' END) + URLTAGS + '/' + REPLACE(LOWER(t.TagName),' ','-') + '/' as URL,0 as Archived,t.TagId,3 from ( + SELECT m.TabId, ss.ModuleId, SettingValue,SettingName FROM {databaseOwner}[{objectQualifier}ModuleSettings] as ss + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = ss.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE (SettingName = 'URLBASE' OR SettingName = 'URLTAGS') AND tb.PortalID = @PortalId + ) as s + PIVOT (MAX(SettingValue) for SettingName in (urlbase,URLTAGS)) as pu + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Tags] as t ON t.ModuleId = pu.ModuleId AND t.IsCategory = 0 + UNION + SELECT DISTINCT tb.TabID,m.ModuleId, g.ForumGroupId,f.ForumId ,COALESCE(t.TopicId, r.TopicId) AS TopicId, + (CASE WHEN s1.SettingValue <> '' THEN s1.SettingValue + '/' Else '' END) + g.PrefixURL + '/' + f.PrefixURL + '/' + ISNULL(ISNULL(rt.URL,t.URL),'') + '/' + COALESCE('likes',s2.SettingValue) + '/' + LTRIM(STR(c.ContentId)) + '/' as URL, 0 as Archived,c.ContentId AS OtherId,4 as URLType + FROM {databaseOwner}[{objectQualifier}activeforums_Content] as c + LEFT OUTER JOIN {databaseOwner}[{objectQualifier}activeforums_Topics] as t ON t.ContentId = c.ContentId + LEFT OUTER JOIN {databaseOwner}[{objectQualifier}activeforums_Replies] as r ON r.ContentId = c.ContentId + LEFT OUTER JOIN {databaseOwner}[{objectQualifier}activeforums_Topics] as rt ON rt.TopicId = r.TopicId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_ForumTopics] as ft ON ft.TopicId = COALESCE(t.TopicId, r.TopicId) + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Forums] as f ON f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Groups] as g ON g.ForumGroupId = f.ForumGroupId + INNER JOIN {databaseOwner}[{objectQualifier}ModuleSettings] as s1 ON s1.ModuleId = f.ModuleId AND s1.SettingName = 'URLBASE' + LEFT OUTER JOIN {databaseOwner}[{objectQualifier}ModuleSettings] as s2 ON s2.ModuleId = f.ModuleId AND s2.SettingName = 'URLLIKES' + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = f.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE c.IsDeleted = 0 AND tb.PortalID = @PortalId AND ISNULL(ISNULL(rt.URL,t.URL),'') <> '' AND ISNULL(f.PrefixURL,'') <> '' + + ) as urls + WHERE LOWER(urls.URL) = @URL +GO + +/* issue 1409 - end - add recycle bin to toolbar */ /* ---------------------- */ From 4af0d606aec292e7ab1c0c1845365eb301d0be32 Mon Sep 17 00:00:00 2001 From: John Henley Date: Thu, 24 Jul 2025 22:06:10 +0000 Subject: [PATCH 15/40] Add 'empty recycle bin' and 'restore all' functionality --- .github/copilot-instructions.md | 87 ------ .../SharedResources.de-DE.resx | 6 + .../SharedResources.es-ES.resx | 6 + .../SharedResources.fr-FR.resx | 6 + .../SharedResources.it-IT.resx | 6 + .../SharedResources.nl-NL.resx | 6 + .../App_LocalResources/SharedResources.resx | 6 + .../Controllers/ForumTopicController.cs | 31 ++ .../Controllers/ReplyController.cs | 44 ++- .../Controllers/TopicController.cs | 47 +-- .../Entities/ForumTopicInfo.cs | 2 +- .../SqlDataProvider/SqlDataProvider.cs | 9 +- Dnn.CommunityForums/ReleaseNotes.txt | 11 +- .../Services/Controllers/TopicController.cs | 2 +- .../Services/ModerationService.cs | 2 +- Dnn.CommunityForums/class/DataProvider.cs | 6 +- .../components/Topics/TopicsController.cs | 6 +- .../controls/af_modtopics.ascx.cs | 6 +- Dnn.CommunityForums/controls/af_post.ascx.cs | 2 + .../controls/af_recycle_bin.ascx | 6 +- .../controls/af_recycle_bin.ascx.cs | 79 ++++- .../sql/09.01.00.SqlDataProvider | 289 ++++++++++++++++-- .../sql/Uninstall.SqlDataProvider | 37 ++- 23 files changed, 529 insertions(+), 173 deletions(-) delete mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index e9ca2ee30..000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,87 +0,0 @@ - - - -You are a senior .NET developer, experienced in C#, JavaScript, HTML, ASP.NET Framework 4.8, CSS, and SQL. -You understand the priciples of DNN (DotNetNuke) and how to develop DNN modules. -You use Visual Studio Enterprise for running, debugging, and testing DNN (DotNetNuke) modules. - -## Code Style and Structure -- Write idiomatic and efficient C# code. -- Follow .NET conventions. -- Follow DNN module development best practices. -- Always follow DNN's StyleCop rules for C# and JavaScript. -- Always use StyleCop to add file header license to all C# files. -- Always put using directives inside the namespace, sorted alphabetically and grouped by system and third-party libraries. -- Always put a blank link between system and third-party libraries using directives. -- Use Razor syntax when possible for component-based UI development. -- Async/await should be used where applicable to ensure non-blocking UI operations. -- Classes should always be internal unless they are intended to be public APIs. -- Always add using directives for namespaces that are used in the file, and remove unused using directives. -- Never use 'Active Forums' in code, always use 'DNN Community Forums' instead. - -## Naming Conventions -- Follow PascalCase for component names, method names, and public members. -- Use underscore prefix and then PascalCase for private fields. -- Use camelCase for local variables. -- Prefix interface names with "I" (e.g., IUserService). - -## .NET Specific Guidelines -- Leverage DNN Dependency Injection for services when possible. -- DNN modules should be developed using DNN version 9.11 and compatible with .NET Framework 4.8. -- Always use the latest stable version of .NET libraries and packages compatible with DNN. -- Use NuGet packages for third-party libraries, ensuring they are compatible with DNN and .NET Framework 4.8. -- Use C# compatible with .NET Framework 4.8, avoiding features exclusive to .NET Core or .NET 5+. - -## Error Handling and Validation -- Implement proper error handling for Web API calls. - -## DNN Entities, Controllers, and Services -- Always create and use DNN-style entities (e.g., ForumUserInfo, ForumPostInfo) for data representation. -- Always create and use DNN-style services (e.g., IForumService, IUserService) for business logic. -- Always create controllers in Controllers folder, and use the appropriate namespaces, such as DotNetNuke.Modules.ActiveForums.Controllers. -- Always create controllers that inherit from DotNetNuke.Modules.ActiveForums.Controllers.RepositoryControllerBase for the appropriate entity. -- Always create controllers as internal classes, unless they are intended to be public APIs. -- Always create an entity class for each DNN entity, such as ForumUserInfo, ForumPostInfo, etc., in the Entities folder, and use namespace DotNetNuke.Modules.ActiveForums.Entities. -- Entity classes should use DNN DAL2 PetaPoco standards, and include TableName and PrimaryKey attributes, for example: - [TableName("activeforums_Content")] - [PrimaryKey("ContentId", AutoIncrement = true)] -- Always map entity's DateUpdated and DateCreated properties, ensuring they are stored in UTC format. -- Always add using DotNetNuke.ComponentModel.DataAnnotations; to the entity class files. - -## SQL DataProvider and Database Access -- Use DNN's built-in DataProvider pattern for database access. -- Create SQL scripts for database migrations and updates, ensuring they are compatible with DNN's upgrade process. -- Always use GETUTCDATE() for date and time storage in the database. -- Always add DateCreated and DataModified to all tables. -- Always default DateCreated to GETUTCDATE() and DateUpdated to GETUTCDATE() on insert and update operations. -- Database object names should be in camelCase. -- Always prefix database object names with activeforums_ to avoid conflicts with other modules. -- Always prefix database objects (tables, views, stored procedures) with the {databaseOwner}{objectQualifier} prefixes to ensure compatibility with DNN's multi-tenant architecture. -- Examples : {databaseOwner}[{objectQualifier}activeforums_ForumPosts] for ForumPosts table, IX_{objectQualifier}activeforums_Content_ModuleId for an index. -- Always add an auto-incrementing identity as primary key to all tables, using the naming convention PK_{objectQualifier}activeforums_{TableName} (e.g., PK_activeforums_ForumPosts). -- Always add indexes to frequently queried columns, using the naming convention IX_{objectQualifier}activeforums_{TableName}_{ColumnName} (e.g., IX_activeforums_ForumPosts_ModuleId). -- Always use NOT EXISTS check when creating tables and adding columns ot existing tables. -- Always use EXISTS check with DROP statements when creating indexes. -- Always use parameterized queries to prevent SQL injection attacks. -- Always create a matching entity class when creating a new table, such as ForumUserInfo, ForumPostInfo, etc., in the Entities folder. -- When adding a new SqlDataProvider, always offer to update the DNN manifest file (DnnCommunityForums.dnn) to reference the new version, ensuring it is compatible with DNN's upgrade process. - -## Caching Strategies -- Implement in-memory caching for all Controllers and services to improve performance and reduce database load. -- Caching should use methods in Cache.cs, such as ContentCacheRetrieve, ContentCacheStore, and ContentCacheRemove for content, and SettingsCache, SettingsCacheRetrieve, and SettingsCacheStore for settings. - -## API Design and Integration -- Use HttpClient or other appropriate services to communicate with external APIs or your own backend. -- Implement error handling for API calls using try-catch and provide proper user feedback in the UI. - -## Testing and Debugging in Visual Studio -- Create unit tests for all public and internal methods using NUnit. -- Use Moq for mocking dependencies during tests, leveraging TestBase.cs for shared test setup. -- Create unit tests in DnnCommunityForumsTests project for testing DNN module functionality. - -## Security and Authentication -- All user properties should be accesed using ForumUserInfo and then DNN UserInfo. -- Use HTTPS for all web communication and ensure proper CORS policies are implemented. - -## API Documentation -- Ensure XML documentation for models and API methods for enhancing sufficient documentation. \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx index eaa3966fc..9e64af23f 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx @@ -1868,4 +1868,10 @@ Von Sind Sie sicher, dass Sie diesen Artikel wiederherstellen möchten? + + Papierkorb leeren + + + Alle wiederherstellen + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx index 8a01f8829..bf07df0e7 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx @@ -1867,4 +1867,10 @@ De ¿Está seguro de que desea restaurar este artículo? + + Papelera de reciclaje vacía + + + Restaurar todo + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx index 2887311fa..ffd07f75f 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx @@ -1864,4 +1864,10 @@ De, Êtes-vous sûr de vouloir restaurer cet article ? + + Corbeille vide + + + Restaurer tout + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx index 385046001..f3b684ca0 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx @@ -1867,4 +1867,10 @@ Da Sei sicuro di voler ripristinare questo oggetto? + + Svuota il cestino + + + Ripristina tutto + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx index 4ac7dc9ff..cc4c97796 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx @@ -1903,4 +1903,10 @@ Van, Weet je zeker dat je dit item wilt restaureren? + + Prullenbak legen + + + Alles herstellen + \ No newline at end of file diff --git a/Dnn.CommunityForums/App_LocalResources/SharedResources.resx b/Dnn.CommunityForums/App_LocalResources/SharedResources.resx index e06668b69..9ccc5db92 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.resx @@ -1867,4 +1867,10 @@ From, Are you sure you wish to restore this item? + + Empty Recycle Bin + + + Restore All + \ No newline at end of file diff --git a/Dnn.CommunityForums/Controllers/ForumTopicController.cs b/Dnn.CommunityForums/Controllers/ForumTopicController.cs index c8c0fdc02..202604d92 100644 --- a/Dnn.CommunityForums/Controllers/ForumTopicController.cs +++ b/Dnn.CommunityForums/Controllers/ForumTopicController.cs @@ -51,6 +51,37 @@ internal DotNetNuke.Modules.ActiveForums.Entities.ForumTopicInfo GetForumForTopi return forumTopic; } + internal DotNetNuke.Modules.ActiveForums.Entities.ForumTopicInfo GetForForumIdTopicId(int forumId, int topicId) + { + return this.Find("WHERE ForumId = @0 AND TopicId = @1", forumId, topicId).FirstOrDefault(); + } + + internal void Update(int forumId, int topicId) + { + var forumTopic = this.GetForForumIdTopicId(forumId, topicId); + if (forumTopic == null) + { + forumTopic = new DotNetNuke.Modules.ActiveForums.Entities.ForumTopicInfo { + ForumId = forumId, + TopicId = topicId, + LastReplyId = null, + }; + this.Insert(forumTopic); + } + + var replies = new DotNetNuke.Modules.ActiveForums.Controllers.ReplyController(this.moduleId).GetByTopicId(topicId); + if (replies.Any()) + { + forumTopic.LastReplyId = replies.Max(r => r.ReplyId); + } + else + { + forumTopic.LastReplyId = null; + } + + this.Update(forumTopic); + } + internal void DeleteForForum(int forumId) { this.Delete("WHERE ForumId = @0", forumId); diff --git a/Dnn.CommunityForums/Controllers/ReplyController.cs b/Dnn.CommunityForums/Controllers/ReplyController.cs index e8441ee2b..12e79e154 100644 --- a/Dnn.CommunityForums/Controllers/ReplyController.cs +++ b/Dnn.CommunityForums/Controllers/ReplyController.cs @@ -18,14 +18,14 @@ // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using DotNetNuke.Modules.ActiveForums.Enums; - namespace DotNetNuke.Modules.ActiveForums.Controllers { using System; + using System.Collections.Generic; using System.Linq; using System.Web; + using DotNetNuke.Modules.ActiveForums.Enums; using DotNetNuke.Modules.ActiveForums.Services.ProcessQueue; using DotNetNuke.Services.FileSystem; using DotNetNuke.Services.Log.EventLog; @@ -71,6 +71,11 @@ public DotNetNuke.Modules.ActiveForums.Entities.ReplyInfo GetById(int replyId) return ri; } + public IEnumerable GetByTopicId(int topicId) + { + return this.Find("WHERE TopicId = @0", topicId); + } + public DotNetNuke.Modules.ActiveForums.Entities.ReplyInfo GetByContentId(int contentId) { var cachekey = string.Format(CacheKeys.ReplyInfoByContentId, this.moduleId, contentId); @@ -103,24 +108,24 @@ public DotNetNuke.Modules.ActiveForums.Entities.ReplyInfo GetByContentId(int con public void Reply_Delete(int portalId, int forumId, int topicId, int replyId, DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior delBehavior) { - var ri = this.GetById(replyId); + var reply = this.GetById(replyId); DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Reply_Delete(forumId, topicId, replyId, (int)delBehavior); - DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_SaveToForum(forumId, topicId, replyId); /* this updates LastReplyId in ForumTopics */ + new DotNetNuke.Modules.ActiveForums.Controllers.ForumTopicController(reply.ModuleId).Update(forumId: forumId, topicId: topicId); DotNetNuke.Modules.ActiveForums.Controllers.ForumController.UpdateForumLastUpdates(forumId); - DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForForum(ri.ModuleId, ri.ForumId); - DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForReply(ri.ModuleId, ri.ReplyId); - DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForTopic(ri.ModuleId, ri.TopicId); - DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForContent(ri.ModuleId, ri.ContentId); - DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(ri.ModuleId, string.Format(CacheKeys.ForumViewPrefix, ri.ModuleId)); - DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(ri.ModuleId, string.Format(CacheKeys.TopicViewPrefix, ri.ModuleId)); - DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(ri.ModuleId, string.Format(CacheKeys.TopicsViewPrefix, ri.ModuleId)); + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForForum(reply.ModuleId, reply.ForumId); + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForReply(reply.ModuleId, reply.ReplyId); + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForTopic(reply.ModuleId, reply.TopicId); + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForContent(reply.ModuleId, reply.ContentId); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(reply.ModuleId, string.Format(CacheKeys.ForumViewPrefix, reply.ModuleId)); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(reply.ModuleId, string.Format(CacheKeys.TopicViewPrefix, reply.ModuleId)); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(reply.ModuleId, string.Format(CacheKeys.TopicsViewPrefix, reply.ModuleId)); new Social().DeleteJournalItemForPost(portalId, forumId, topicId, replyId); - Utilities.UpdateModuleLastContentModifiedOnDate(ri.ModuleId); - if (delBehavior != 0) + Utilities.UpdateModuleLastContentModifiedOnDate(reply.ModuleId); + if (delBehavior.Equals(DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior.Recycle)) { return; } @@ -148,11 +153,18 @@ public void Reply_Delete(int portalId, int forumId, int topicId, int replyId, Do public void Restore(int portalId, int forumId, int topicId, int replyId) { var reply = this.GetById(replyId); + + // if restoring reply, also restore topic if necessary + if (reply.Topic.IsDeleted) + { + new DotNetNuke.Modules.ActiveForums.Controllers.TopicController(reply.ModuleId).Restore(portalId, forumId, topicId); + } + reply.IsDeleted = false; this.Update(reply); reply.Content.IsDeleted = false; new DotNetNuke.Modules.ActiveForums.Controllers.ContentController().Update(reply.Content); - DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_SaveToForum(forumId, topicId, replyId); /* this updates LastReplyId in ForumTopics */ + new DotNetNuke.Modules.ActiveForums.Controllers.ForumTopicController(reply.ModuleId).Update(forumId: forumId, topicId: topicId); DotNetNuke.Modules.ActiveForums.Controllers.ForumController.UpdateForumLastUpdates(forumId); @@ -231,7 +243,7 @@ public int Reply_Save(int portalId, int moduleId, DotNetNuke.Modules.ActiveForum } int replyId = Convert.ToInt32(DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Reply_Save(portalId, ri.TopicId, ri.ReplyId, ri.ReplyToId, ri.StatusId, ri.IsApproved, ri.IsDeleted, ri.Content.Subject.Trim(), ri.Content.Body.Trim(), ri.Content.DateCreated, ri.Content.DateUpdated, ri.Content.AuthorId, ri.Content.AuthorName, ri.Content.IPAddress)); - DotNetNuke.Modules.ActiveForums.Controllers.TopicController.SaveToForum(moduleId, ri.ForumId, ri.TopicId, replyId); + DotNetNuke.Modules.ActiveForums.Controllers.TopicController.SaveToForum(moduleId, ri.ForumId, ri.TopicId); DotNetNuke.Modules.ActiveForums.Controllers.ForumController.UpdateForumLastUpdates(ri.ForumId); return replyId; } @@ -248,7 +260,7 @@ public DotNetNuke.Modules.ActiveForums.Entities.ReplyInfo ApproveReply(int porta reply.IsApproved = true; rc.Reply_Save(portalId, moduleId, reply); - DotNetNuke.Modules.ActiveForums.Controllers.TopicController.SaveToForum(moduleId, forumId, topicId, replyId); + DotNetNuke.Modules.ActiveForums.Controllers.TopicController.SaveToForum(moduleId, forumId, topicId); if (forum.FeatureSettings.ModApproveNotify && reply.Author.AuthorId > 0) { diff --git a/Dnn.CommunityForums/Controllers/TopicController.cs b/Dnn.CommunityForums/Controllers/TopicController.cs index 674f36a6e..e457d3334 100644 --- a/Dnn.CommunityForums/Controllers/TopicController.cs +++ b/Dnn.CommunityForums/Controllers/TopicController.cs @@ -18,6 +18,8 @@ // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using DotNetNuke.Collections; + namespace DotNetNuke.Modules.ActiveForums.Controllers { using System; @@ -29,6 +31,7 @@ namespace DotNetNuke.Modules.ActiveForums.Controllers using DotNetNuke.Common.Utilities; using DotNetNuke.Modules.ActiveForums.Enums; using DotNetNuke.Modules.ActiveForums.Services.ProcessQueue; + using DotNetNuke.Modules.ActiveForums.ViewModels; using DotNetNuke.Services.FileSystem; using DotNetNuke.Services.Log.EventLog; using DotNetNuke.Services.Social.Notifications; @@ -150,7 +153,7 @@ public static int QuickCreate(int portalId, int moduleId, int forumId, string su topicId = DotNetNuke.Modules.ActiveForums.Controllers.TopicController.Save(ti); if (topicId > 0) { - DotNetNuke.Modules.ActiveForums.Controllers.TopicController.SaveToForum(moduleId, forumId, topicId, -1); + DotNetNuke.Modules.ActiveForums.Controllers.TopicController.SaveToForum(moduleId, forumId, topicId); } return topicId; @@ -252,13 +255,22 @@ public static void Move(int moduleId, int userId, int topicId, int newForumId) DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForContent(ti.ModuleId, ti.ContentId); } + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. Not Used.")] public static void SaveToForum(int moduleId, int forumId, int topicId, int lastReplyId = -1) { - DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_SaveToForum(forumId, topicId, lastReplyId); + SaveToForum(moduleId: moduleId, forumId: forumId, topicId: topicId); + } + + public static void SaveToForum(int moduleId, int forumId, int topicId) + { + new DotNetNuke.Modules.ActiveForums.Controllers.ForumTopicController(moduleId).Update(forumId: forumId, topicId: topicId); Utilities.UpdateModuleLastContentModifiedOnDate(moduleId); DotNetNuke.Modules.ActiveForums.Controllers.ForumController.UpdateForumLastUpdates(forumId); DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForForum(moduleId, forumId); DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForTopic(moduleId, topicId); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(moduleId, string.Format(CacheKeys.ForumViewPrefix, moduleId)); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(moduleId, string.Format(CacheKeys.TopicViewPrefix, moduleId)); + DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(moduleId, string.Format(CacheKeys.TopicsViewPrefix, moduleId)); } public static int Save(DotNetNuke.Modules.ActiveForums.Entities.TopicInfo ti) @@ -289,7 +301,7 @@ public static int Save(DotNetNuke.Modules.ActiveForums.Entities.TopicInfo ti) } // TODO: convert to use DAL2? - return Convert.ToInt32(DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_Save(ti.Forum.PortalId, ti.TopicId, ti.ViewCount, ti.ReplyCount, ti.IsLocked, ti.IsPinned, ti.TopicIcon, ti.StatusId, ti.IsApproved, ti.IsDeleted, ti.IsAnnounce, ti.IsArchived, ti.AnnounceStart ?? Utilities.NullDate(), ti.AnnounceEnd ?? Utilities.NullDate(), ti.Content.Subject.Trim(), ti.Content.Body.Trim(), ti.Content.Summary.Trim(), ti.Content.DateCreated, ti.Content.DateUpdated, ti.Content.AuthorId, ti.Content.AuthorName, ti.Content.IPAddress, (int)ti.TopicType, ti.Priority, ti.TopicUrl, ti.TopicData)); + return Convert.ToInt32(DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_Save(ti.Forum.PortalId, ti.ModuleId, ti.TopicId, ti.ViewCount, ti.ReplyCount, ti.IsLocked, ti.IsPinned, ti.TopicIcon, ti.StatusId, ti.IsApproved, ti.IsDeleted, ti.IsAnnounce, ti.IsArchived, ti.AnnounceStart ?? Utilities.NullDate(), ti.AnnounceEnd ?? Utilities.NullDate(), ti.Content.Subject.Trim(), ti.Content.Body.Trim(), ti.Content.Summary.Trim(), ti.Content.DateCreated, ti.Content.DateUpdated, ti.Content.AuthorId, ti.Content.AuthorName, ti.Content.IPAddress, (int)ti.TopicType, ti.Priority, ti.TopicUrl, ti.TopicData)); } public void Restore(int portalId, int forumId, int topicId) @@ -299,29 +311,30 @@ public void Restore(int portalId, int forumId, int topicId) this.Update(topic); topic.Content.IsDeleted = false; new DotNetNuke.Modules.ActiveForums.Controllers.ContentController().Update(topic.Content); - DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_SaveToForum(forumId, topicId, topic.LastReplyId); - - DotNetNuke.Modules.ActiveForums.Controllers.ForumController.UpdateForumLastUpdates(forumId); - - DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForForum(topic.ModuleId, topic.ForumId); - DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForReply(topic.ModuleId, topic.ReplyId); - DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForTopic(topic.ModuleId, topic.TopicId); + SaveToForum(topic.ModuleId, forumId, topicId); DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForContent(topic.ModuleId, topic.ContentId); - DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(topic.ModuleId, string.Format(CacheKeys.ForumViewPrefix, topic.ModuleId)); - DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(topic.ModuleId, string.Format(CacheKeys.TopicViewPrefix, topic.ModuleId)); - DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(topic.ModuleId, string.Format(CacheKeys.TopicsViewPrefix, topic.ModuleId)); - - Utilities.UpdateModuleLastContentModifiedOnDate(topic.ModuleId); } + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. Use DeleteById(int topicId, DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior deleteBehavior)")] public void DeleteById(int topicId) + { + DotNetNuke.Modules.ActiveForums.Entities.TopicInfo ti = this.GetById(topicId); + DeleteById(topicId, SettingsBase.GetModuleSettings(ti.ModuleId).DeleteBehavior); + } + + public void DeleteById(int topicId, DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior deleteBehavior) { DotNetNuke.Modules.ActiveForums.Entities.TopicInfo ti = this.GetById(topicId); if (ti != null) { new Social().DeleteJournalItemForPost(ti.PortalId, ti.ForumId, topicId, 0); - DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_Delete(ti.ForumId, topicId, (int)SettingsBase.GetModuleSettings(ti.ModuleId).DeleteBehavior ); + var replyController = new DotNetNuke.Modules.ActiveForums.Controllers.ReplyController(ti.ModuleId); + replyController.GetByTopicId(topicId).ForEach(reply => + { + DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForReply(reply.ModuleId, reply.ReplyId); + }); + DotNetNuke.Modules.ActiveForums.DataProvider.Instance().Topics_Delete(ti.ForumId, topicId, (int)deleteBehavior); Utilities.UpdateModuleLastContentModifiedOnDate(ti.ModuleId); DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForForum(ti.ModuleId, ti.ForumId); DotNetNuke.Modules.ActiveForums.DataCache.ContentCacheClearForTopic(ti.ModuleId, ti.TopicId); @@ -330,7 +343,7 @@ public void DeleteById(int topicId) DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(ti.ModuleId, string.Format(CacheKeys.TopicViewPrefix, ti.ModuleId)); DotNetNuke.Modules.ActiveForums.DataCache.CacheClearPrefix(ti.ModuleId, string.Format(CacheKeys.TopicsViewPrefix, ti.ModuleId)); - if (SettingsBase.GetModuleSettings(ti.ModuleId).DeleteBehavior != DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior.Remove) + if (deleteBehavior.Equals(DotNetNuke.Modules.ActiveForums.Enums.DeleteBehavior.Recycle)) { return; } diff --git a/Dnn.CommunityForums/Entities/ForumTopicInfo.cs b/Dnn.CommunityForums/Entities/ForumTopicInfo.cs index 61233ee36..aca36913b 100644 --- a/Dnn.CommunityForums/Entities/ForumTopicInfo.cs +++ b/Dnn.CommunityForums/Entities/ForumTopicInfo.cs @@ -33,6 +33,6 @@ internal class ForumTopicInfo public int TopicId { get; set; } - public int LastReplyId { get; set; } = 0; + public int? LastReplyId { get; set; } } } diff --git a/Dnn.CommunityForums/Providers/DataProviders/SqlDataProvider/SqlDataProvider.cs b/Dnn.CommunityForums/Providers/DataProviders/SqlDataProvider/SqlDataProvider.cs index e547895f4..4445fd072 100644 --- a/Dnn.CommunityForums/Providers/DataProviders/SqlDataProvider/SqlDataProvider.cs +++ b/Dnn.CommunityForums/Providers/DataProviders/SqlDataProvider/SqlDataProvider.cs @@ -568,11 +568,18 @@ public override void Topics_Move(int PortalId, int ModuleId, int ForumId, int To SqlHelper.ExecuteNonQuery(this.ConnectionString, this.DatabaseOwner + this.ObjectQualifier + "activeforums_Topics_Move", PortalId, ModuleId, ForumId, TopicId); } + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. No longer used.")] public override int Topics_Save(int PortalId, int TopicId, int ViewCount, int ReplyCount, bool IsLocked, bool IsPinned, string TopicIcon, int StatusId, bool IsApproved, bool IsDeleted, bool IsAnnounce, bool IsArchived, DateTime AnnounceStart, DateTime AnnounceEnd, string Subject, string Body, string Summary, DateTime DateCreated, DateTime DateUpdated, int AuthorId, string AuthorName, string IPAddress, int TopicType, int priority, string URL, string TopicData) { - return Convert.ToInt32(SqlHelper.ExecuteScalar(this.ConnectionString, this.DatabaseOwner + this.ObjectQualifier + "activeforums_Topics_Save", PortalId, TopicId, ViewCount, ReplyCount, IsLocked, IsPinned, TopicIcon, StatusId, IsApproved, IsDeleted, IsAnnounce, IsArchived, this.GetNull(AnnounceStart), this.GetNull(AnnounceEnd), Subject, Body, Summary, DateCreated, DateUpdated, AuthorId, AuthorName, IPAddress, TopicType, priority, URL, TopicData)); + throw new NotImplementedException(); + } + + public override int Topics_Save(int PortalId, int ModuleId, int TopicId, int ViewCount, int ReplyCount, bool IsLocked, bool IsPinned, string TopicIcon, int StatusId, bool IsApproved, bool IsDeleted, bool IsAnnounce, bool IsArchived, DateTime AnnounceStart, DateTime AnnounceEnd, string Subject, string Body, string Summary, DateTime DateCreated, DateTime DateUpdated, int AuthorId, string AuthorName, string IPAddress, int TopicType, int priority, string URL, string TopicData) + { + return Convert.ToInt32(SqlHelper.ExecuteScalar(this.ConnectionString, this.DatabaseOwner + this.ObjectQualifier + "activeforums_Topics_Save", PortalId, ModuleId, TopicId, ViewCount, ReplyCount, IsLocked, IsPinned, TopicIcon, StatusId, IsApproved, IsDeleted, IsAnnounce, IsArchived, this.GetNull(AnnounceStart), this.GetNull(AnnounceEnd), Subject, Body, Summary, DateCreated, DateUpdated, AuthorId, AuthorName, IPAddress, TopicType, priority, URL, TopicData)); } + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. No longer used.")] public override int Topics_SaveToForum(int ForumId, int TopicId, int LastReplyId) { return Convert.ToInt32(SqlHelper.ExecuteScalar(this.ConnectionString, this.DatabaseOwner + this.ObjectQualifier + "activeforums_Topics_SaveToForum", ForumId, TopicId, this.GetNull(LastReplyId))); diff --git a/Dnn.CommunityForums/ReleaseNotes.txt b/Dnn.CommunityForums/ReleaseNotes.txt index 6e2436ed9..701bc77d7 100644 --- a/Dnn.CommunityForums/ReleaseNotes.txt +++ b/Dnn.CommunityForums/ReleaseNotes.txt @@ -45,8 +45,6 @@

DNN Community Forums Release Notes

-
-

09.01.00

@@ -54,24 +52,21 @@

THANK YOU for all of the valuable contributions by @johnhenley - . - .

What's to follow are all of the relevant updates that have occurred during the development cycle of this release.

+ -->.

New Features & Enhancements

    +
  • NEW: Recycle Bin (Issue 515)
  • None at this time.
  • + -->

    - +-->

@@ -48,7 +48,7 @@

09.01.00

- . +-->

New Features & Enhancements

    -
  • NEW: Recycle Bin (Issue 515)
  • +
  • NEW: Adds Recycle Bin to be able to restore (soft-)deleted topics and replies (Issue 515)
  • +
  • NEW: Adds an avatar injection service to populate avatars (currently using Gravatar) for forums users who haven't set up their own DNN profile picture/avatar. (Issue 349)
  • +-->

Tasks / Development Updates (and Technical Debt)

    -
  • Update indexes on these forum/topic tracking tables to make unique combination of UserId/TopicId and UserId/ForumId; Create a script to detect and remove duplicates, keeping the latest entries (Issue 1434)
  • +
  • Update indexes on forum/topic tracking tables for uniqueness; upgrade script to detect and remove duplicates, keeping the latest entries (Issue 1434)
  • +
  • Categories and topic categories were previously handled as tags with an 'isCategory' flag; they are now first-class entites (Issue 1441)
  • Update Web API method security to not require ForumId for certain methods (e.g. user Ban) (Issue 1507)
  • -
  • Refactors Web API methods security checking to use Role Ids rather than string array of Role names; also refactors Topic view model and Topic update panel (Issue 1526)
  • +
  • Refactor Web API methods security checking to use Role Ids rather than string array of Role names; refactor Topic view model and Topic update panel (Issue 1526)
  • 09.01.00 Release Prep (Issue TBD)
  • - +-->

diff --git a/Dnn.CommunityForums/Services/Controllers/TopicController.cs b/Dnn.CommunityForums/Services/Controllers/TopicController.cs index de9be375b..381f78545 100644 --- a/Dnn.CommunityForums/Services/Controllers/TopicController.cs +++ b/Dnn.CommunityForums/Services/Controllers/TopicController.cs @@ -433,47 +433,47 @@ public HttpResponseMessage Update(TopicDto2 dto) int topicId = dto.Topic.TopicId; if (topicId > 0 && forumId > 0) - { - DotNetNuke.Modules.ActiveForums.Entities.TopicInfo originalTopic = new DotNetNuke.Modules.ActiveForums.Controllers.TopicController(this.ForumModuleId).GetById(topicId); - if (originalTopic != null) { + DotNetNuke.Modules.ActiveForums.Entities.TopicInfo originalTopic = new DotNetNuke.Modules.ActiveForums.Controllers.TopicController(this.ForumModuleId).GetById(topicId); + if (originalTopic != null) + { var forumUser = new DotNetNuke.Modules.ActiveForums.Controllers.ForumUserController(this.ForumModuleId).GetByUserId(this.ActiveModule.PortalID, this.UserInfo.UserID); if (this.UserInfo.IsAdmin || this.UserInfo.IsSuperUser || Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.ModerateRoleIds, forumUser.UserRoleIds) || (Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.EditRoleIds, forumUser.UserRoleIds) && this.UserInfo.UserID == originalTopic.Content.AuthorId) ) - { - string subject = Utilities.XSSFilter(dto.Topic.Subject, true); - originalTopic.Content.Subject = subject; - originalTopic.TopicUrl = DotNetNuke.Modules.ActiveForums.Controllers.UrlController.BuildTopicUrlSegment(portalId: this.ActiveModule.PortalID, moduleId: this.ForumModuleId, topicId: topicId, subject: subject, forumInfo: originalTopic.Forum); - - if (dto.Topic.IsLocked != originalTopic.IsLocked && - (this.UserInfo.IsAdmin || - this.UserInfo.IsSuperUser || - Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.ModerateRoleIds, forumUser.UserRoleIds) || - (Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.LockRoleIds, forumUser.UserRoleIds) && this.UserInfo.UserID == originalTopic.Content.AuthorId) - ) - ) { - originalTopic.IsLocked = dto.Topic.IsLocked; - } + string subject = Utilities.XSSFilter(dto.Topic.Subject, true); + originalTopic.Content.Subject = subject; + originalTopic.TopicUrl = DotNetNuke.Modules.ActiveForums.Controllers.UrlController.BuildTopicUrlSegment(portalId: this.ActiveModule.PortalID, moduleId: this.ForumModuleId, topicId: topicId, subject: subject, forumInfo: originalTopic.Forum); - if (dto.Topic.IsPinned != originalTopic.IsPinned && - (this.UserInfo.IsAdmin || - this.UserInfo.IsSuperUser || - Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.ModerateRoleIds, forumUser.UserRoleIds) || - (Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.PinRoleIds, forumUser.UserRoleIds) && this.UserInfo.UserID == originalTopic.Content.AuthorId) - ) - ) - { - originalTopic.IsLocked = dto.Topic.IsLocked; - } + if (dto.Topic.IsLocked != originalTopic.IsLocked && + (this.UserInfo.IsAdmin || + this.UserInfo.IsSuperUser || + Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.ModerateRoleIds, forumUser.UserRoleIds) || + (Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.LockRoleIds, forumUser.UserRoleIds) && this.UserInfo.UserID == originalTopic.Content.AuthorId) + ) + ) + { + originalTopic.IsLocked = dto.Topic.IsLocked; + } - originalTopic.Priority = dto.Topic.Priority; - originalTopic.StatusId = dto.Topic.StatusId; + if (dto.Topic.IsPinned != originalTopic.IsPinned && + (this.UserInfo.IsAdmin || + this.UserInfo.IsSuperUser || + Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.ModerateRoleIds, forumUser.UserRoleIds) || + (Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.PinRoleIds, forumUser.UserRoleIds) && this.UserInfo.UserID == originalTopic.Content.AuthorId) + ) + ) + { + originalTopic.IsLocked = dto.Topic.IsLocked; + } + + originalTopic.Priority = dto.Topic.Priority; + originalTopic.StatusId = dto.Topic.StatusId; - if (originalTopic.Forum.Properties != null && originalTopic.Forum.Properties.Count > 0) + if (originalTopic.Forum.Properties != null && originalTopic.Forum.Properties.Count > 0) { StringBuilder tData = new StringBuilder(); tData.Append(""); @@ -503,47 +503,47 @@ public HttpResponseMessage Update(TopicDto2 dto) originalTopic.TopicData = tData.ToString(); } - DotNetNuke.Modules.ActiveForums.Controllers.TopicController.Save(originalTopic); - Utilities.UpdateModuleLastContentModifiedOnDate(this.ForumModuleId); + DotNetNuke.Modules.ActiveForums.Controllers.TopicController.Save(originalTopic); + Utilities.UpdateModuleLastContentModifiedOnDate(this.ForumModuleId); - if (this.UserInfo.IsAdmin || - this.UserInfo.IsSuperUser || - Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.ModerateRoleIds, forumUser.UserRoleIds) || - (Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.TagRoleIds, forumUser.UserRoleIds) && this.UserInfo.UserID == originalTopic.Content.AuthorId) - ) - { - if (!string.IsNullOrEmpty(dto.Topic.Tags)) + if (this.UserInfo.IsAdmin || + this.UserInfo.IsSuperUser || + Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.ModerateRoleIds, forumUser.UserRoleIds) || + (Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.TagRoleIds, forumUser.UserRoleIds) && this.UserInfo.UserID == originalTopic.Content.AuthorId) + ) { - DataProvider.Instance().Tags_DeleteByTopicId(this.ActiveModule.PortalID, this.ForumModuleId, topicId); - string tagForm = dto.Topic.Tags; - string[] tags = tagForm.Split(','); - foreach (string tag in tags) + if (!string.IsNullOrEmpty(dto.Topic.Tags)) { - string sTag = Utilities.CleanString(this.ActiveModule.PortalID, tag.Trim(), false, EditorTypes.TEXTBOX, false, false, this.ForumModuleId, string.Empty, false); - DataProvider.Instance().Tags_Save(this.ActiveModule.PortalID, this.ForumModuleId, -1, sTag, 0, 1, 0, topicId, false, -1, -1); + new DotNetNuke.Modules.ActiveForums.Controllers.TopicTagController().DeleteForTopic(topicId); + string tagForm = dto.Topic.Tags; + string[] tags = tagForm.Split(','); + foreach (string tag in tags) + { + string sTag = Utilities.CleanString(this.ActiveModule.PortalID, tag.Trim(), false, EditorTypes.TEXTBOX, false, false, this.ForumModuleId, string.Empty, false); + DataProvider.Instance().Tags_Save(this.ActiveModule.PortalID, this.ForumModuleId, -1, sTag, 0, topicId); + } } } - } - if (this.UserInfo.IsAdmin || - this.UserInfo.IsSuperUser || - Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.ModerateRoleIds, forumUser.UserRoleIds) || - (Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.EditRoleIds, forumUser.UserRoleIds) && this.UserInfo.UserID == originalTopic.Content.AuthorId) - ) + if (this.UserInfo.IsAdmin || + this.UserInfo.IsSuperUser || + Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.ModerateRoleIds, forumUser.UserRoleIds) || + (Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(originalTopic.Forum.Security.EditRoleIds, forumUser.UserRoleIds) && this.UserInfo.UserID == originalTopic.Content.AuthorId) + ) { if (!string.IsNullOrEmpty(dto.Topic.SelectedCategoriesAsString)) { string[] cats = dto.Topic.SelectedCategoriesAsString.Split(';'); - DataProvider.Instance().Tags_DeleteTopicToCategory(this.ActiveModule.PortalID, this.ForumModuleId, -1, topicId); + new DotNetNuke.Modules.ActiveForums.Controllers.TopicCategoryController().DeleteForTopic(topicId); foreach (string c in cats) { int cid = -1; - if (!string.IsNullOrEmpty(c) && SimulateIsNumeric.IsNumeric(c)) + if (!string.IsNullOrEmpty(c) && Utilities.IsNumeric(c)) { cid = Convert.ToInt32(c); if (cid > 0) { - DataProvider.Instance().Tags_AddTopicToCategory(this.ActiveModule.PortalID, this.ForumModuleId, cid, topicId); + new DotNetNuke.Modules.ActiveForums.Controllers.TopicCategoryController().AddCategoryToTopic(cid, topicId); } } } @@ -557,7 +557,7 @@ public HttpResponseMessage Update(TopicDto2 dto) return this.Request.CreateResponse(HttpStatusCode.Unauthorized); } - return this.Request.CreateResponse(HttpStatusCode.NotFound, dto.Topic); + return this.Request.CreateResponse(HttpStatusCode.NotFound, dto.Topic); } } catch (Exception ex) diff --git a/Dnn.CommunityForums/class/DataProvider.cs b/Dnn.CommunityForums/class/DataProvider.cs index 40d90bc99..6917411e1 100644 --- a/Dnn.CommunityForums/class/DataProvider.cs +++ b/Dnn.CommunityForums/class/DataProvider.cs @@ -232,24 +232,37 @@ private static void CreateProvider() public abstract int Subscriptions_IsSubscribed(int portalId, int moduleId, int forumId, int topicId, int mode, int userId); #endregion + #region Categories + public abstract IDataReader Categories_List(int portalId, int moduleId, int pageIndex, int pageSize, string sort, string sortColumn, int forumId, int forumGroupId); + #endregion + #region Tags [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. No longer used.")] public abstract void Tags_Delete(int portalId, int moduleId, int tagId); + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. No longer used.")] public abstract void Tags_DeleteByTopicId(int portalId, int moduleId, int topicId); [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. No longer used.")] public abstract IDataReader Tags_Get(int portalId, int moduleId, int tagId); - public abstract IDataReader Tags_List(int portalId, int moduleId, bool isCategory, int pageIndex, int pageSize, string sort, string sortColumn, int forumId, int forumGroupId); + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. No longer used.")] + public abstract IDataReader Tags_List(int PortalId, int ModuleId, bool IsCategory, int PageIndex, int PageSize, string Sort, string SortColumn, int ForumId, int ForumGroupId); - public abstract int Tags_Save(int portalId, int moduleId, int tagId, string tagName, int clicks, int items, int priority, int topicId, bool isCategory, int forumId, int forumGroupId); + public abstract IDataReader Tags_List(int PortalId, int ModuleId, int PageIndex, int PageSize, string Sort, string SortColumn); + + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. No longer used.")] + public abstract int Tags_Save(int portalId, int moduleId, int tagId, string tagName, int clicks, int items, int priority, int topicId, int forumId, int forumGroupId); + + public abstract int Tags_Save(int portalId, int moduleId, int tagId, string tagName, int items, int topicId); [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. No longer used.")] public abstract IDataReader Tags_Search(int portalId, int moduleId, string search); + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. No longer used.")] public abstract void Tags_AddTopicToCategory(int portalId, int moduleId, int tagId, int topicId); + [Obsolete("Deprecated in Community Forums. Removed in 10.00.00. No longer used.")] public abstract void Tags_DeleteTopicToCategory(int portalId, int moduleId, int tagId, int topicId); #endregion diff --git a/Dnn.CommunityForums/controls/admin_categories.ascx b/Dnn.CommunityForums/controls/admin_categories.ascx index efb2e8317..6deb92fd4 100644 --- a/Dnn.CommunityForums/controls/admin_categories.ascx +++ b/Dnn.CommunityForums/controls/admin_categories.ascx @@ -65,25 +65,36 @@ function amaf_deleteCategory(row){ [RESX:CategoryName]: - +
- - - + +
[RESX:CategoryName]
[RESX:TagClicks]
[RESX:TagItems]
 
+ + + + + + + + - + - - + - +
+
[RESX:CategoryName]
+
[RESX:Items]
+
+
 
+
diff --git a/Dnn.CommunityForums/controls/admin_categories.ascx.cs b/Dnn.CommunityForums/controls/admin_categories.ascx.cs index ca4ef3467..692220fa0 100644 --- a/Dnn.CommunityForums/controls/admin_categories.ascx.cs +++ b/Dnn.CommunityForums/controls/admin_categories.ascx.cs @@ -23,6 +23,7 @@ namespace DotNetNuke.Modules.ActiveForums using System; using System.Linq; using System.Web.UI.WebControls; + using DotNetNuke.Modules.ActiveForums.Data; public partial class admin_categories : ActiveAdminBase { @@ -55,10 +56,10 @@ private void agCategories_Callback(object sender, Controls.CallBackEventArgs e) { case "DELETE": { - int tagId = Convert.ToInt32(e.Parameters[4].Split(':')[1]); - if (Utilities.IsNumeric(tagId)) + int categoryId = Convert.ToInt32(e.Parameters[4].Split(':')[1]); + if (Utilities.IsNumeric(categoryId)) { - new DotNetNuke.Modules.ActiveForums.Controllers.TagController().DeleteById(tagId); + new DotNetNuke.Modules.ActiveForums.Controllers.CategoryController().DeleteById(categoryId); } break; @@ -67,13 +68,13 @@ private void agCategories_Callback(object sender, Controls.CallBackEventArgs e) case "SAVE": { string[] sParams = e.Parameters[4].Split(':'); - string tagName = sParams[1].Trim(); - int tagId = 0; + string categoryName = sParams[1].Trim(); + int categoryId = 0; int forumId = -1; int forumGroupId = -1; if (sParams.Length > 2) { - tagId = Convert.ToInt32(sParams[2]); + categoryId = Convert.ToInt32(sParams[2]); } if (sParams[3].Contains("FORUM")) @@ -86,9 +87,38 @@ private void agCategories_Callback(object sender, Controls.CallBackEventArgs e) forumGroupId = Convert.ToInt32(sParams[3].Replace("GROUP", string.Empty)); } - if (!(tagName == string.Empty)) + if (!(categoryName == string.Empty)) { - DataProvider.Instance().Tags_Save(this.PortalId, this.ModuleId, tagId, tagName, 0, 0, 0, -1, true, forumId, forumGroupId); + var categoryController = new DotNetNuke.Modules.ActiveForums.Controllers.CategoryController(); + DotNetNuke.Modules.ActiveForums.Entities.CategoryInfo category = null; + if (categoryId > 0) + { + category = categoryController.GetById(categoryId); + } + + if (category != null) + { + category.CategoryName = categoryName; + category.ForumId = forumId; + category.ForumGroupId = forumGroupId; + category.PortalId = this.PortalId; + category.ModuleId = this.ModuleId; + categoryController.Update(category); + categoryController.RecountItems(category.CategoryId); + } + else + { + category = new DotNetNuke.Modules.ActiveForums.Entities.CategoryInfo() + { + CategoryName = categoryName, + ForumId = forumId, + ForumGroupId = forumGroupId, + PortalId = this.PortalId, + ModuleId = this.ModuleId, + Items = 0, + }; + categoryController.Insert(category); + } } break; @@ -101,7 +131,7 @@ private void agCategories_Callback(object sender, Controls.CallBackEventArgs e) int pageSize = Convert.ToInt32(e.Parameters[1]); string sortColumn = e.Parameters[2].ToString(); string sort = e.Parameters[3].ToString(); - this.agCategories.Datasource = DataProvider.Instance().Tags_List(this.PortalId, this.ModuleId, true, pageIndex, pageSize, sort, sortColumn, -1, -1); + this.agCategories.Datasource = DataProvider.Instance().Categories_List(this.PortalId, this.ModuleId, pageIndex, pageSize, sort, sortColumn, -1, -1); this.agCategories.Refresh(e.Output); } catch (Exception ex) @@ -136,9 +166,7 @@ private void BindGroups() private void agCategories_ItemBound(object sender, Modules.ActiveForums.Controls.ItemBoundEventArgs e) { - // e.Item(1) = Server.HtmlEncode(e.Item(1).ToString) - // e.Item(2) = Server.HtmlEncode(e.Item(2).ToString) - e.Item[6] = "\"""; + e.Item[5] = "\"""; } } } diff --git a/Dnn.CommunityForums/controls/admin_tags.ascx b/Dnn.CommunityForums/controls/admin_tags.ascx index 6c532ddf8..52a1bd694 100644 --- a/Dnn.CommunityForums/controls/admin_tags.ascx +++ b/Dnn.CommunityForums/controls/admin_tags.ascx @@ -48,14 +48,25 @@ function amaf_deleteTag(row){
- - + +
[RESX:TagName]
[RESX:TagClicks]
[RESX:TagItems]
 
+ + + + + + - diff --git a/Dnn.CommunityForums/controls/admin_tags.ascx.cs b/Dnn.CommunityForums/controls/admin_tags.ascx.cs index adb0fc908..7f32e6c64 100644 --- a/Dnn.CommunityForums/controls/admin_tags.ascx.cs +++ b/Dnn.CommunityForums/controls/admin_tags.ascx.cs @@ -72,7 +72,33 @@ private void agTags_Callback(object sender, Modules.ActiveForums.Controls.CallBa if (!(tagName == string.Empty)) { - DataProvider.Instance().Tags_Save(this.PortalId, this.ModuleId, tagId, tagName, 0, 0, 0, -1, false, -1, -1); + var tagController = new DotNetNuke.Modules.ActiveForums.Controllers.TagController(); + DotNetNuke.Modules.ActiveForums.Entities.TagInfo tag = null; + if (tagId > 0) + { + tag = tagController.GetById(tagId); + } + + if (tag != null) + { + tag.TagName = tagName; + tag.PortalId = this.PortalId; + tag.ModuleId = this.ModuleId; + tagController.Update(tag); + tagController.RecountItems(tag.TagId); + } + else + { + tag = new DotNetNuke.Modules.ActiveForums.Entities.TagInfo() + { + + TagName = tagName, + PortalId = this.PortalId, + ModuleId = this.ModuleId, + Items = 0, + }; + tagController.Insert(tag); + } } break; @@ -85,7 +111,7 @@ private void agTags_Callback(object sender, Modules.ActiveForums.Controls.CallBa int pageSize = Convert.ToInt32(e.Parameters[1]); string sortColumn = e.Parameters[2].ToString(); string sort = e.Parameters[3].ToString(); - this.agTags.Datasource = DataProvider.Instance().Tags_List(this.PortalId, this.ModuleId, false, pageIndex, pageSize, sort, sortColumn, -1, -1); + this.agTags.Datasource = DataProvider.Instance().Tags_List(this.PortalId, this.ModuleId, pageIndex, pageSize, sort, sortColumn); this.agTags.Refresh(e.Output); } catch (Exception ex) @@ -95,9 +121,7 @@ private void agTags_Callback(object sender, Modules.ActiveForums.Controls.CallBa private void agTags_ItemBound(object sender, Modules.ActiveForums.Controls.ItemBoundEventArgs e) { - // e.Item(1) = Server.HtmlEncode(e.Item(1).ToString) - // e.Item(2) = Server.HtmlEncode(e.Item(2).ToString) - e.Item[4] = "\"""; + e.Item[3] = "\"""; } #endregion diff --git a/Dnn.CommunityForums/controls/af_post.ascx.cs b/Dnn.CommunityForums/controls/af_post.ascx.cs index 19525d64a..ea9c791cc 100644 --- a/Dnn.CommunityForums/controls/af_post.ascx.cs +++ b/Dnn.CommunityForums/controls/af_post.ascx.cs @@ -26,6 +26,7 @@ namespace DotNetNuke.Modules.ActiveForums using System.IO; using System.Linq; using System.Runtime.Serialization.Json; + using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Web; @@ -810,7 +811,7 @@ private void SaveTopic() this.SaveAttachments(ti.ContentId); if (DotNetNuke.Modules.ActiveForums.Controllers.PermissionController.HasRequiredPerm(this.ForumInfo.Security.TagRoleIds, this.ForumUser.UserRoleIds)) { - new DotNetNuke.Modules.ActiveForums.Controllers.TopicTagController().DeleteForTopicId(this.TopicId); + new DotNetNuke.Modules.ActiveForums.Controllers.TopicTagController().DeleteForTopic(this.TopicId); var tagForm = string.Empty; if (this.Request.Form["txtTags"] != null) { @@ -818,12 +819,12 @@ private void SaveTopic() } if (tagForm != string.Empty) - { + { var tags = tagForm.Split(','); foreach (var tag in tags) { var sTag = Utilities.CleanString(this.PortalId, tag.Trim(), false, EditorTypes.TEXTBOX, false, false, this.ForumModuleId, string.Empty, false); - DataProvider.Instance().Tags_Save(this.PortalId, this.ForumModuleId, -1, sTag, 0, 1, 0, this.TopicId, false, -1, -1); + DataProvider.Instance().Tags_Save(this.PortalId, this.ForumModuleId, -1, sTag, 0, this.TopicId); } } } @@ -833,7 +834,7 @@ private void SaveTopic() if (this.Request.Form["amaf-catselect"] != null) { var cats = this.Request.Form["amaf-catselect"].Split(';'); - DataProvider.Instance().Tags_DeleteTopicToCategory(this.PortalId, this.ForumModuleId, -1, this.TopicId); + new DotNetNuke.Modules.ActiveForums.Controllers.TopicCategoryController().DeleteForTopic(this.TopicId); foreach (var c in cats) { if (string.IsNullOrEmpty(c) || !Utilities.IsNumeric(c)) @@ -844,7 +845,8 @@ private void SaveTopic() var cid = Convert.ToInt32(c); if (cid > 0) { - DataProvider.Instance().Tags_AddTopicToCategory(this.PortalId, this.ForumModuleId, cid, this.TopicId); + new DotNetNuke.Modules.ActiveForums.Controllers.TopicCategoryController().AddCategoryToTopic(cid, this.TopicId); + } } } diff --git a/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider index b80fa4675..3c69af40d 100644 --- a/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider +++ b/Dnn.CommunityForums/sql/09.01.00.SqlDataProvider @@ -23,7 +23,7 @@ WITH ft_max AS ( SELECT ft.ForumId, ft.UserId, MAX(TrackingId) AS TrackingId FROM {databaseOwner}[{objectQualifier}activeforums_Forums_Tracking] ft GROUP BY ft.ForumId, ft.UserId -) +) , ft_multiples AS ( SELECT ft.ForumId, ft.UserId, ft_max.TrackingId FROM {databaseOwner}[{objectQualifier}activeforums_Forums_Tracking] ft @@ -91,10 +91,10 @@ GO /* create new indexes */ CREATE UNIQUE NONCLUSTERED INDEX [IX_{objectQualifier}activeforums_Topics_Tracking_Opt1] ON {databaseOwner}{objectQualifier}activeforums_Topics_Tracking -( + ( [UserId] ASC, [TopicId] ASC -) + ) INCLUDE ( [LastReplyId] ) WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) @@ -341,7 +341,7 @@ END @Tags as Tags FROM - ( +( SELECT T.ForumId, T.TopicId, T.ReplyId, T.Subject, T.Summary, T.AuthorId, T.StatusId, IsNull(T.AuthorName,'anon') as AuthorName, IsNull(T.Username,IsNull(T.AuthorName,'anon')) as Username, IsNull(T.FirstName,'') as FirstName, IsNull(T.LastName,'') as LastName, IsNull(T.DisplayName,T.AuthorName) as DisplayName, @@ -365,7 +365,7 @@ END {databaseOwner}{objectQualifier}activeforums_Content AS C ON T.ContentId = C.ContentId LEFT OUTER JOIN {databaseOwner}{objectQualifier}activeforums_UserProfiles AS P ON C.AuthorId = P.UserId AND P.PortalId = @PortalId WHERE (T.TopicId = @TopicId) - ) +) AS TopicWithRowNumbers WHERE RowRank > @RowIndex AND RowRank <= (@RowIndex + @MaxRows) @@ -396,7 +396,7 @@ GO CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Forums_LastUpdates] @ForumId int AS - + DECLARE @TopicDateCreated datetime DECLARE @ReplyDateCreated datetime DECLARE @Subject nvarchar(250) @@ -412,7 +412,7 @@ DECLARE @TopicDateCreated datetime SET @ReplyDateCreated = (SELECT MAX(datecreated) from {databaseOwner}{objectQualifier}activeforums_content as c inner join {databaseOwner}{objectQualifier}activeforums_replies as r on r.contentid = c.contentid and r.isapproved = 1 and r.isdeleted = 0 inner join {databaseOwner}{objectQualifier}activeforums_topics as t on t.topicid = r.topicid inner join {databaseOwner}{objectQualifier}activeforums_forumTopics as ft on ft.topicid = t.topicid WHERE ft.forumid = @ForumId) IF @ReplyDateCreated > @TopicDateCreated - BEGIN +BEGIN SET @LastDateTime = @ReplyDateCreated SET @Subject = (SELECT TOP 1 c.Subject from {databaseOwner}{objectQualifier}activeforums_content as c inner join {databaseOwner}{objectQualifier}activeforums_replies as r on r.contentid = c.contentid and r.isapproved = 1 and r.isdeleted = 0 inner join {databaseOwner}{objectQualifier}activeforums_topics as t on t.topicid = r.topicid inner join {databaseOwner}{objectQualifier}activeforums_forumTopics as ft on ft.topicid = t.topicid WHERE ft.forumid = @ForumId ORDER BY c.DateCreated DESC) @@ -439,7 +439,7 @@ DECLARE @TopicDateCreated datetime SET @ReplyId = 0 - END +END DECLARE @TotalReplies int DECLARE @TotalTopics int SET @TotalReplies = (SELECT Count(ReplyId) from {databaseOwner}{objectQualifier}activeforums_replies as r inner join {databaseOwner}{objectQualifier}activeforums_topics as t on t.topicid = r.topicid and r.isapproved = 1 and r.isdeleted = 0 INNER JOIN {databaseOwner}{objectQualifier}activeforums_forumtopics as ft on t.topicid = ft.topicid WHERE ft.forumid = @ForumId) @@ -472,7 +472,7 @@ FROM {databaseOwner}{objectQualifier}activeforums_Forums AS F RIGHT OUTE {databaseOwner}{objectQualifier}activeforums_Forums AS P ON F.ParentForumId = P.ForumId WHERE (G.ModuleId = @ModuleId) ORDER BY G.SortOrder, F.SortOrder - + ELSE IF @ForumGroupId > 0 AND @ParentForumId = -1 AND @FillLastPost = 0 --Return Forums in Group @@ -520,7 +520,7 @@ GO /* vw_activeforums_ForumView - remove LastPostId */ IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'{databaseOwner}{objectQualifier}vw_activeforums_ForumView')) DROP VIEW {databaseOwner}{objectQualifier}vw_activeforums_ForumView -GO +GO CREATE VIEW {databaseOwner}[{objectQualifier}vw_activeforums_ForumView] AS SELECT TOP (100) PERCENT G.ForumGroupId, G.ModuleId, G.GroupName, F.ForumId, F.ForumName, F.ForumDesc, F.TotalTopics, F.TotalReplies, F.ParentForumId, @@ -540,13 +540,12 @@ WHERE (G.Active = 1) AND (F.Active = 1) GO - /* begin: activeforums_Reply_Save */ IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Reply_Save]') AND type in (N'P', N'PC')) DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Reply_Save] GO - + CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Reply_Save] @PortalId int, @TopicId int, @@ -587,7 +586,7 @@ END IF EXISTS(SELECT ContentId FROM {databaseOwner}{objectQualifier}activeforums_Replies WHERE ReplyId = @ReplyId) - BEGIN +BEGIN SELECT @ContentId = ContentId, @ApprovedStatus = IsApproved FROM {databaseOwner}{objectQualifier}activeforums_Replies WHERE ReplyId = @ReplyId BEGIN @@ -609,7 +608,7 @@ IF EXISTS(SELECT ContentId FROM {databaseOwner}{objectQualifier}activeforums_Rep IsDeleted = @IsDeleted, ReplyToId = @ReplyToId WHERE ReplyId = @ReplyId - END +END END ELSE --INSERT @@ -627,9 +626,9 @@ BEGIN VALUES (@ContentId, @TopicId, @StatusId, @IsApproved, @IsDeleted, @ReplyToId) SET @ReplyId = SCOPE_IDENTITY() - + END - + END IF @IsApproved = 1 @@ -687,9 +686,9 @@ IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{objectQuali DROP INDEX [IX_{objectQualifier}activeforums_UserProfiles_Opt2] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles GO CREATE NONCLUSTERED INDEX [IX_{objectQualifier}activeforums_UserProfiles_Opt2] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles -( + ( [UserId] ASC -) + ) INCLUDE ( [TopicCount], [ReplyCount], [ViewCount], @@ -713,10 +712,10 @@ IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{objectQuali DROP INDEX [IX_{objectQualifier}activeforums_UserProfiles_Opt3] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles GO CREATE NONCLUSTERED INDEX [IX_{objectQualifier}activeforums_UserProfiles_Opt3] ON {databaseOwner}{objectQualifier}activeforums_UserProfiles -( + ( [PortalId] ASC, [UserId] ASC -) + ) INCLUDE ( [ProfileId], [TopicCount], [ReplyCount], @@ -751,10 +750,10 @@ ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_UserProfiles] DROP COL GO IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_{objectQualifier}activeforums_UserProfiles_AvatarType]') AND type = 'D') ALTER TABLE {databaseOwner}{objectQualifier}activeforums_UserProfiles DROP CONSTRAINT DF_{objectQualifier}activeforums_UserProfiles_AvatarType -GO +GO IF EXISTS(SELECT * FROM sys.columns WHERE [name] = N'AvatarType' AND [object_id] = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_UserProfiles]')) ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_UserProfiles] DROP COLUMN AvatarType -GO +GO /* add AvatarLastRefresh to activeforums_UserProfiles */ IF NOT EXISTS(SELECT * FROM SYS.COLUMNS WHERE Name = N'AvatarLastRefresh' and Object_ID = Object_ID(N'{databaseOwner}[{objectQualifier}activeforums_UserProfiles]')) @@ -815,10 +814,10 @@ IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{objectQuali DROP INDEX [IX_{objectQualifier}activeforums_Content_IsDeleted] ON {databaseOwner}{objectQualifier}activeforums_Content GO CREATE NONCLUSTERED INDEX [IX_{objectQualifier}activeforums_Content_IsDeleted] ON {databaseOwner}{objectQualifier}activeforums_Content -( + ( [ModuleId] ASC, [ContentId] ASC -) + ) WHERE IsDeleted = 1 WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) GO @@ -828,10 +827,10 @@ IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{objectQuali DROP INDEX [IX_{objectQualifier}activeforums_Topics_IsDeleted] ON {databaseOwner}{objectQualifier}activeforums_Topics GO CREATE NONCLUSTERED INDEX [IX_{objectQualifier}activeforums_Topics_IsDeleted] ON {databaseOwner}{objectQualifier}activeforums_Topics -( + ( [TopicId] ASC, [ContentId] ASC -) + ) WHERE IsDeleted = 1 WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) GO @@ -891,13 +890,13 @@ WHERE TopicId = @TopicId exec {databaseOwner}{objectQualifier}activeforums_Forums_LastUpdates @ForumId + -- reset thread order EXEC {databaseOwner}{objectQualifier}activeforums_SaveTopicNextPrev @ForumId GO - /* begin: update activeforums_Topics_Save to pass ModuleId */ IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_Save]') AND type in (N'P', N'PC')) DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Topics_Save] @@ -932,16 +931,13 @@ CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Topics_Save] @TopicData nvarchar(max) AS DECLARE @ContentId int -DECLARE @ForumId int -DECLARE @ForumGroupId int -SET @ForumId = -1 DECLARE @ApprovedStatus bit SET @ApprovedStatus = @IsApproved DECLARE @currURL nvarchar(1000) IF @URL <> '' AND @TopicId>0 BEGIN - SET @ForumId = (SELECT ForumId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics WHERE TopicId = @TopicId) - SET @ForumGroupId = (SELECT ForumGroupId FROM {databaseOwner}{objectQualifier}activeforums_Forums WHERE ForumId= @ForumId) + DECLARE @ForumId int = (SELECT ForumId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics WHERE TopicId = @TopicId) + DECLARE @ForumGroupId int = (SELECT ForumGroupId FROM {databaseOwner}{objectQualifier}activeforums_Forums WHERE ForumId= @ForumId) SET @currURL = {databaseOwner}{objectQualifier}fn_activeforums_GetURL(@ModuleId,@ForumGroupId,@ForumId,@TopicId,-1,-1) IF @currURL <> '' BEGIN @@ -1026,6 +1022,102 @@ GO /* end : activeforums_Topics_Save */ +/* activeforums_Groups_Delete */ +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Groups_Delete]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Groups_Delete] +GO +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Groups_Delete] +@ModuleId int, +@ForumGroupId int +AS +DECLARE @GroupSettingsKey nvarchar(25) +SET @GroupSettingsKey = (SELECT GroupSettingsKey FROM {databaseOwner}{objectQualifier}activeforums_Groups WHERE ForumGroupId = @ForumGroupId AND ModuleId = @ModuleId) + +/*DELETE REPLY ATTACHMENTS*/ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Attachments + WHERE AttachId IN ( + SELECT ca.AttachId FROM {databaseOwner}{objectQualifier}activeforums_Content_Attachments as ca + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Content as c on c.ContentId = ca.ContentId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Replies as r on c.ContentId = r.ContentId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft on ft.TopicId = r.TopicId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Groups as g on g.ForumGroupId = f.ForumGroupId + WHERE g.ForumGroupId = @ForumGroupId) +/*DELETE TOPIC ATTACHMENTS*/ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Attachments + WHERE AttachId IN ( + SELECT ca.AttachId FROM {databaseOwner}{objectQualifier}activeforums_Content_Attachments as ca + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Content as c on c.ContentId = ca.ContentId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Topics as t on c.ContentId = t.ContentId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft on ft.TopicId = t.TopicId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Groups as g on g.ForumGroupId = f.ForumGroupId + WHERE g.ForumGroupId = @ForumGroupId) + +/*DELETE REPLY CONTENT */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Content WHERE ContentId IN ( + Select r.ContentId FROM {databaseOwner}{objectQualifier}activeforums_Replies as r + INNER JOIN {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft on ft.TopicId = r.TopicId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Groups as g on g.ForumGroupId = f.ForumGroupId + WHERE g.ForumGroupId = @ForumGroupId) + +/*DELETE REPLIES */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Replies + WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f ON f.ForumId = ft.ForumID + WHERE f.ForumGroupId = @ForumGroupId) + +/*DELETE TOPIC CONTENT */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Content WHERE ContentId IN ( + Select t.ContentId FROM {databaseOwner}{objectQualifier}activeforums_Topics as t + INNER JOIN {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft on ft.TopicId = t.TopicId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Groups as g on g.ForumGroupId = f.ForumGroupId + WHERE g.ForumGroupId = @ForumGroupId) +/*DELETE TOPICS*/ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft INNER JOIN + {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId + WHERE f.ForumGroupId = @ForumGroupId) + +/*DELETE FORUM TRACKING*/ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Forums_Tracking + WHERE ForumId IN ( + SELECT ForumId FROM {databaseOwner}{objectQualifier}activeforums_Forums WHERE ForumGroupId = @ForumGroupId) +/*DELETE FORUM SETTINGS */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Settings + WHERE ModuleId = @ModuleId AND GroupKey IN ( + SELECT ForumSettingsKey FROM {databaseOwner}{objectQualifier}activeforums_Forums WHERE ForumGroupId = @ForumGroupId) +/*DELETE FORUM SUBSCRIPTIONS */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Subscriptions + WHERE ForumId IN ( + SELECT ForumId FROM {databaseOwner}{objectQualifier}activeforums_Forums WHERE ForumGroupId = @ForumGroupId) +/*DELETE TOPICS CATEGORIES */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Categories + WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId WHERE f.ForumGroupId = @ForumGroupId) +/*DELETE TOPICS TAGS */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Tags + WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId WHERE f.ForumGroupId = @ForumGroupId) +/*DELETE TOPICS TRACKING */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Tracking + WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId WHERE f.ForumGroupId = @ForumGroupId) +/*DELETE TOPICS RATINGS */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Ratings + WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId WHERE f.ForumGroupId = @ForumGroupId) +/*DELETE FORUMTOPICS TABLE */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics + WHERE ForumId IN ( + SELECT f.ForumId FROM {databaseOwner}{objectQualifier}activeforums_Forums as f WHERE f.ForumGroupId = @ForumGroupId) +/*DELETE FORUMS */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Forums WHERE ForumGroupId = @ForumGroupId +GO + /* issues 1406 - end - indexes on IsDeleted columns in activeforums_Content, activeforums_Topics and activeforums_Replies tables */ @@ -1113,7 +1205,7 @@ SELECT TabId, ModuleID, ForumGroupId, ForumId, TopicId, Url,Archived,OtherId,Url WHERE (SettingName = 'URLBASE' OR SettingName = 'URLCATS') AND tb.PortalID = @PortalId ) as s PIVOT (MAX(SettingValue) for SettingName in (urlbase,URLCATS)) as pu - INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Tags] as t ON t.ModuleId = pu.ModuleId AND t.IsCategory = 1 + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Tags] as t ON t.ModuleId = pu.ModuleId UNION SELECT TabId, pu.ModuleId,-1 as ForumGroupId,-1 as ForumId,-1 as TopicId, (CASE WHEN UrlBase <> '' THEN UrlBase + '/' Else '' END) + URLTAGS + '/' + REPLACE(LOWER(t.TagName),' ','-') + '/' as URL,0 as Archived,t.TagId,3 from ( @@ -1123,7 +1215,7 @@ SELECT TabId, ModuleID, ForumGroupId, ForumId, TopicId, Url,Archived,OtherId,Url WHERE (SettingName = 'URLBASE' OR SettingName = 'URLTAGS') AND tb.PortalID = @PortalId ) as s PIVOT (MAX(SettingValue) for SettingName in (urlbase,URLTAGS)) as pu - INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Tags] as t ON t.ModuleId = pu.ModuleId AND t.IsCategory = 0 + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Tags] as t ON t.ModuleId = pu.ModuleId UNION SELECT DISTINCT tb.TabID,m.ModuleId, g.ForumGroupId,f.ForumId ,COALESCE(t.TopicId, r.TopicId) AS TopicId, (CASE WHEN s1.SettingValue <> '' THEN s1.SettingValue + '/' Else '' END) + g.PrefixURL + '/' + f.PrefixURL + '/' + ISNULL(ISNULL(rt.URL,t.URL),'') + '/' + COALESCE('likes',s2.SettingValue) + '/' + LTRIM(STR(c.ContentId)) + '/' as URL, 0 as Archived,c.ContentId AS OtherId,4 as URLType @@ -1145,8 +1237,749 @@ SELECT TabId, ModuleID, ForumGroupId, ForumId, TopicId, Url,Archived,OtherId,Url GO /* issue 1409 - end - add recycle bin to toolbar */ + /* ---------------------- */ +/* issue 1411 - begin - separate categories from tags */ + +/* activeforums_Categories - Create table */ + +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Categories]') AND type in (N'U')) +CREATE TABLE {databaseOwner}[{objectQualifier}activeforums_Categories]( + [CategoryId] [int] IDENTITY(1,1) NOT NULL, + [PortalId] [int] NOT NULL, + [ModuleId] [int] NOT NULL, + [CategoryName] [nvarchar](255) NOT NULL, + [Clicks] [int] NOT NULL CONSTRAINT [DF_{objectQualifier}activeforums_Categories_Clicks] DEFAULT ((0)), + [Items] [int] NOT NULL CONSTRAINT [DF_{objectQualifier}activeforums_Categories_Items] DEFAULT ((0)), + [Priority] [int] NOT NULL CONSTRAINT [DF_{objectQualifier}activeforums_Categories_Priority] DEFAULT ((0)), + [ForumId] INT CONSTRAINT [DF_{objectQualifier}activeforums_Categories_ForumId] DEFAULT ((-1)) NOT NULL, + [ForumGroupId] INT CONSTRAINT [DF_{objectQualifier}activeforums_Categories_ForumGroupId] DEFAULT ((-1)) NOT NULL, + CONSTRAINT [PK_{objectQualifier}activeforums_Categories] PRIMARY KEY CLUSTERED +( + [CategoryId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +) +GO + +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Categories') AND name = N'IX_{objectQualifier}activeforums_Categories_Alt1') +DROP INDEX [IX_{objectQualifier}activeforums_Categories_Alt1] ON {databaseOwner}{objectQualifier}activeforums_Categories +GO +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Categories') AND name = N'IX_{objectQualifier}activeforums_Categories_Alt1') +CREATE UNIQUE NONCLUSTERED INDEX IX_{objectQualifier}activeforums_Categories_Alt1 ON {databaseOwner}[{objectQualifier}activeforums_Categories] + ( + PortalId, + ModuleId, + CategoryName, + CategoryId + ) +GO + +/* activeforums_Topics_Categories - Create table */ + +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_Categories]') AND type in (N'U')) +CREATE TABLE {databaseOwner}[{objectQualifier}activeforums_Topics_Categories]( + [TopicCategoryId] [int] IDENTITY(1,1) NOT NULL, + [TopicId] [int] NOT NULL, + [CategoryId] [int] NOT NULL, + CONSTRAINT [PK_{objectQualifier}activeforums_Topics_Categories] PRIMARY KEY CLUSTERED +( + [TopicCategoryId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +) +GO + + +/* migrate existing categories to new table */ + +IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags]') AND name = 'IsCategory') +BEGIN + SET IDENTITY_INSERT {databaseOwner}[{objectQualifier}activeforums_Categories] ON + EXECUTE('INSERT INTO {databaseOwner}[{objectQualifier}activeforums_Categories] (CategoryId, PortalId, ModuleId, CategoryName, Clicks, Items, Priority) + SELECT TagId, PortalId, ModuleId, TagName, Clicks, Items, Priority FROM {databaseOwner}[{objectQualifier}activeforums_Tags] + WHERE IsCategory = 1') + SET IDENTITY_INSERT {databaseOwner}[{objectQualifier}activeforums_Categories] OFF + + EXECUTE('INSERT INTO {databaseOwner}[{objectQualifier}activeforums_Topics_Categories] (TopicId, CategoryId) + SELECT tt.TopicId, tt.TagId FROM {databaseOwner}[{objectQualifier}activeforums_Topics_Tags] tt INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Tags] t ON t.TagId = tt.TagId AND t.IsCategory = 1') +END + +/* Remove old activeforums_Topics_Tags entries that are categories */ +IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags]') AND name = 'IsCategory') +EXECUTE('DELETE tt FROM {databaseOwner}[{objectQualifier}activeforums_Topics_Tags] tt INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Tags] t ON t.TagId = tt.TagId AND t.IsCategory = 1') +GO + +/* Remove Category Rows from activeforums_Tags */ +IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags]') AND name = 'IsCategory') +EXECUTE('DELETE FROM {databaseOwner}[{objectQualifier}activeforums_Tags] WHERE IsCategory = 1') +GO + +/* Remove old isCategory column from activeforums_Tags */ +IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_{objectQualifier}activeforums_Tags_IsCategory]') AND type = 'D') +ALTER TABLE {databaseOwner}{objectQualifier}activeforums_Tags DROP CONSTRAINT DF_{objectQualifier}activeforums_Tags_IsCategory +GO +IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags]') AND name = 'IsCategory') +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Tags] DROP COLUMN IsCategory +GO + +/* Remove ForumGroupId column from activeforums_Tags */ +IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_{objectQualifier}activeforums_Tags_ForumGroupId]') AND type = 'D') +ALTER TABLE {databaseOwner}{objectQualifier}activeforums_Tags DROP CONSTRAINT DF_{objectQualifier}activeforums_Tags_ForumGroupId +GO +IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags]') AND name = 'ForumGroupId') +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Tags] DROP COLUMN ForumGroupId +GO + +/* Remove ForumId column from activeforums_Tags */ +IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_{objectQualifier}activeforums_Tags_ForumId]') AND type = 'D') +ALTER TABLE {databaseOwner}{objectQualifier}activeforums_Tags DROP CONSTRAINT DF_{objectQualifier}activeforums_Tags_ForumId +GO +IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags]') AND name = 'ForumId') +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Tags] DROP COLUMN ForumId +GO + +/* Remove Priority column from activeforums_Tags */ +IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_{objectQualifier}activeforums_Tags_Priority]') AND type = 'D') +ALTER TABLE {databaseOwner}{objectQualifier}activeforums_Tags DROP CONSTRAINT DF_{objectQualifier}activeforums_Tags_Priority +GO +IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags]') AND name = 'Priority') +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Tags] DROP COLUMN Priority +GO +/* Remove Clicks column from activeforums_Tags */ +IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_{objectQualifier}activeforums_Tags_Clicks]') AND type = 'D') +ALTER TABLE {databaseOwner}{objectQualifier}activeforums_Tags DROP CONSTRAINT DF_{objectQualifier}activeforums_Tags_Clicks +GO +IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags]') AND name = 'Clicks') +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Tags] DROP COLUMN Clicks +GO + +/* drop old activeforums_Topics_Tags indexes and replace with new indexes */ + +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Tags') AND name = N'IX_{objectQualifier}activeforums_Topics_Tags_UniqueAlt') +DROP INDEX [IX_{objectQualifier}activeforums_Topics_Tags_UniqueAlt] ON {databaseOwner}{objectQualifier}activeforums_Topics_Tags +GO + +IF (OBJECT_ID(N'{databaseOwner}[PK_{objectQualifier}activeforums_Topics_Tags]', 'PK') IS NOT NULL) +BEGIN + ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Topics_Tags] DROP CONSTRAINT [PK_{objectQualifier}activeforums_Topics_Tags] +END +GO + +IF NOT EXISTS(SELECT * FROM SYS.COLUMNS WHERE Name = N'TopicTagid' and Object_ID = Object_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_Tags]')) +BEGIN + ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Topics_Tags] + ADD TopicTagid INT IDENTITY(1,1) +END +GO + +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Topics_Tags] + ADD CONSTRAINT [PK_{objectQualifier}activeforums_Topics_Tags] PRIMARY KEY CLUSTERED ( [TopicTagid] ASC ) + WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) + +GO + +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Tags') AND name = N'IX_{objectQualifier}activeforums_Topics_Tags_UniqueAlt1') +DROP INDEX [IX_{objectQualifier}activeforums_Topics_Tags_UniqueAlt1] ON {databaseOwner}{objectQualifier}activeforums_Topics_Tags +GO +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Tags') AND name = N'IX_{objectQualifier}activeforums_Topics_Tags_UniqueAlt1') +CREATE UNIQUE NONCLUSTERED INDEX IX_{objectQualifier}activeforums_Topics_Tags_UniqueAlt1 ON {databaseOwner}[{objectQualifier}activeforums_Topics_Tags] + ( + TagId, + TopicId + ) +GO +IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Tags') AND name = N'IX_{objectQualifier}activeforums_Topics_Tags_UniqueAlt2') +DROP INDEX [IX_{objectQualifier}activeforums_Topics_Tags_UniqueAlt2] ON {databaseOwner}{objectQualifier}activeforums_Topics_Tags +GO +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Tags') AND name = N'IX_{objectQualifier}activeforums_Topics_Tags_UniqueAlt2') +CREATE UNIQUE NONCLUSTERED INDEX IX_{objectQualifier}activeforums_Topics_Tags_UniqueAlt2 ON {databaseOwner}[{objectQualifier}activeforums_Topics_Tags] + ( + TopicId, + TagId + ) +GO + +/* activeforums_Categories - cascade delete from Modules */ +IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'{databaseOwner}[FK_{objectQualifier}activeforums_Categories_Modules]') AND parent_object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Categories]')) +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Categories] DROP CONSTRAINT +[FK_{objectQualifier}activeforums_Categories_Modules] +GO +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Categories] ADD CONSTRAINT + [FK_{objectQualifier}activeforums_Categories_Modules] FOREIGN KEY (ModuleId) + REFERENCES {databaseOwner}[{objectQualifier}Modules] (ModuleID) + ON DELETE CASCADE +GO + + +/* activeforums_Topics_Categories - cascade delete from activeforums_Topics */ +IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'{databaseOwner}[FK_{objectQualifier}activeforums_Topics_Categories_Topics]') AND parent_object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_Categories]')) +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Topics_Categories] + DROP CONSTRAINT [FK_{objectQualifier}activeforums_Topics_Categories_Topics] +GO +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Topics_Categories] ADD CONSTRAINT + [FK_{objectQualifier}activeforums_Topics_Categories_Topics] FOREIGN KEY (TopicId) + REFERENCES {databaseOwner}[{objectQualifier}activeforums_Topics] (TopicId) + ON DELETE CASCADE +GO + +/* activeforums_Topics_Categories - create relationship but NOT cascade delete from activeforums_Categories */ +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Categories') AND name = N'IX_{objectQualifier}activeforums_Topics_Categories_UniqueAlt1') +CREATE UNIQUE NONCLUSTERED INDEX IX_{objectQualifier}activeforums_Topics_Categories_UniqueAlt1 ON {databaseOwner}[{objectQualifier}activeforums_Topics_Categories] + ( + CategoryId, + TopicId + ) +GO +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'{databaseOwner}{objectQualifier}activeforums_Topics_Categories') AND name = N'IX_{objectQualifier}activeforums_Topics_Categories_UniqueAlt2') +CREATE UNIQUE NONCLUSTERED INDEX IX_{objectQualifier}activeforums_Topics_Categories_UniqueAlt2 ON {databaseOwner}[{objectQualifier}activeforums_Topics_Categories] + ( + TopicId, + CategoryId + ) +GO + +IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'{databaseOwner}[FK_{objectQualifier}activeforums_Topics_Categories_Categories]') AND parent_object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_Categories]')) +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Topics_Categories] + DROP CONSTRAINT [FK_{objectQualifier}activeforums_Topics_Categories_Categories] +GO +ALTER TABLE {databaseOwner}[{objectQualifier}activeforums_Topics_Categories] ADD CONSTRAINT + [FK_{objectQualifier}activeforums_Topics_Categories_Categories] FOREIGN KEY (CategoryId) + REFERENCES {databaseOwner}[{objectQualifier}activeforums_Categories] (CategoryId) + ON DELETE NO ACTION +GO + +/* update activeforums_Topics_Delete */ + +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_Delete]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Topics_Delete] +GO + +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Topics_Delete] +@ForumId int, +@TopicId int, +@DelBehavior int, +@UpdateStats bit = 1 +AS +DECLARE @ContentId int +SELECT @ContentId = ContentId FROM {databaseOwner}{objectQualifier}activeforums_Topics WHERE TopicId = @TopicId +BEGIN +IF @DelBehavior = 1 + BEGIN + UPDATE {databaseOwner}{objectQualifier}activeforums_Content SET IsDeleted = 1, DateUpdated = GETUTCDATE() WHERE ContentId = @ContentId OR ContentId IN (Select ContentId FROM {databaseOwner}{objectQualifier}activeforums_Replies WHERE TopicId = @TopicId) + UPDATE {databaseOwner}{objectQualifier}activeforums_Topics SET IsDeleted = 1 WHERE TopicId = @TopicId + UPDATE {databaseOwner}{objectQualifier}activeforums_Replies SET IsDeleted = 1 WHERE TopicId = @TopicId + END +ELSE + BEGIN + DELETE FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics WHERE ForumId = @ForumId AND TopicId = @TopicId + DELETE FROM {databaseOwner}{objectQualifier}activeforums_Replies WHERE TopicId = @TopicId + DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics WHERE TopicId = @TopicId + DELETE FROM {databaseOwner}{objectQualifier}activeforums_Content WHERE ContentId = @ContentId + DELETE FROM {databaseOwner}{objectQualifier}activeforums_Content WHERE ContentId IN (Select ContentId FROM {databaseOwner}{objectQualifier}activeforums_Replies WHERE TopicId = @TopicId) + DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Categories WHERE TopicId = @TopicId + DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Tags WHERE TopicId = @TopicId + DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Ratings WHERE TopicId = @TopicId + END +END +exec {databaseOwner}{objectQualifier}activeforums_Forums_LastUpdates @ForumId + + +-- reset thread order +EXEC {databaseOwner}{objectQualifier}activeforums_SaveTopicNextPrev @ForumId +GO + +/*update activeforums_Topics_Delete_For_User */ + +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_Delete_For_User]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Topics_Delete_For_User]; +GO + +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Topics_Delete_For_User] +@ModuleId int, +@UserId int, +@DelBehavior int +AS + +BEGIN + +SET NOCOUNT ON + +IF @DelBehavior = 1 + BEGIN + UPDATE t SET t.IsDeleted = 1 + FROM {databaseOwner}[{objectQualifier}activeforums_Topics] t + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Content] c + ON c.ContentId = t.ContentId + WHERE c.AuthorId = @UserId AND c.ModuleId = @ModuleId and c.IsDeleted = 0 + UPDATE r SET r.IsDeleted = 1 + FROM {databaseOwner}[{objectQualifier}activeforums_Replies] r + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Content] c + ON c.ContentId = r.ContentId + WHERE c.AuthorId = @UserId AND c.ModuleId = @ModuleId and c.IsDeleted = 0 + UPDATE c SET c.IsDeleted = 1, c.DateUpdated = GETUTCDATE() + FROM {databaseOwner}[{objectQualifier}activeforums_Content] c + WHERE c.AuthorId = @UserId AND c.ModuleId = @ModuleId and c.IsDeleted = 0 + END +ELSE + BEGIN + DELETE tr + FROM {databaseOwner}[{objectQualifier}activeforums_Topics_Ratings] tr + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Topics] t + ON t.TopicId = tr.TopicId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Content] c + ON c.ContentId = t.ContentId + WHERE c.AuthorId = @UserId AND c.ModuleId = @ModuleId + DELETE tc + FROM {databaseOwner}[{objectQualifier}activeforums_Topics_Categories] tc + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Topics] t + ON t.TopicId = tc.TopicId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Content] c + ON c.ContentId = t.ContentId + WHERE c.AuthorId = @UserId AND c.ModuleId = @ModuleId + DELETE tt + FROM {databaseOwner}[{objectQualifier}activeforums_Topics_Tags] tt + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Topics] t + ON t.TopicId = tt.TopicId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Content] c + ON c.ContentId = t.ContentId + WHERE c.AuthorId = @UserId AND c.ModuleId = @ModuleId + DELETE ft + FROM {databaseOwner}[{objectQualifier}activeforums_ForumTopics] ft + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Topics] t + ON t.TopicId = ft.TopicId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Content] c + ON c.ContentId = t.ContentId + WHERE c.AuthorId = @UserId AND c.ModuleId = @ModuleId + DELETE t + FROM {databaseOwner}[{objectQualifier}activeforums_Topics] t + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Content] c + ON c.ContentId = t.ContentId + WHERE c.AuthorId = @UserId AND c.ModuleId = @ModuleId + DELETE r + FROM {databaseOwner}[{objectQualifier}activeforums_Replies] r + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Content] c + ON c.ContentId = r.ContentId + WHERE c.AuthorId = @UserId AND c.ModuleId = @ModuleId + DELETE c + FROM {databaseOwner}[{objectQualifier}activeforums_Content] c + WHERE c.AuthorId = @UserId AND c.ModuleId = @ModuleId + END +END + +DECLARE @ForumId INT + +BEGIN + DECLARE forums_cursor CURSOR FOR SELECT ForumId FROM {databaseOwner}[{objectQualifier}activeforums_Forums] WHERE ModuleId = @ModuleId + OPEN forums_cursor + FETCH NEXT FROM forums_cursor into @ForumId + WHILE (@@fetch_status = 0) + BEGIN + EXEC {databaseOwner}[{objectQualifier}activeforums_Forums_LastUpdates] @ForumId + EXEC {databaseOwner}[{objectQualifier}activeforums_SaveTopicNextPrev] @ForumId + FETCH NEXT FROM forums_cursor INTO @ForumId + END + CLOSE forums_cursor + DEALLOCATE forums_cursor +END +GO + +/* activeforums_Groups_Delete */ +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Groups_Delete]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Groups_Delete] +GO +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Groups_Delete] +@ModuleId int, +@ForumGroupId int +AS +DECLARE @GroupSettingsKey nvarchar(25) +SET @GroupSettingsKey = (SELECT GroupSettingsKey FROM {databaseOwner}{objectQualifier}activeforums_Groups WHERE ForumGroupId = @ForumGroupId AND ModuleId = @ModuleId) + +/*DELETE REPLY ATTACHMENTS*/ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Attachments + WHERE AttachId IN ( + SELECT ca.AttachId FROM {databaseOwner}{objectQualifier}activeforums_Content_Attachments as ca + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Content as c on c.ContentId = ca.ContentId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Replies as r on c.ContentId = r.ContentId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft on ft.TopicId = r.TopicId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Groups as g on g.ForumGroupId = f.ForumGroupId + WHERE g.ForumGroupId = @ForumGroupId) +/*DELETE TOPIC ATTACHMENTS*/ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Attachments + WHERE AttachId IN ( + SELECT ca.AttachId FROM {databaseOwner}{objectQualifier}activeforums_Content_Attachments as ca + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Content as c on c.ContentId = ca.ContentId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Topics as t on c.ContentId = t.ContentId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft on ft.TopicId = t.TopicId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Groups as g on g.ForumGroupId = f.ForumGroupId + WHERE g.ForumGroupId = @ForumGroupId) + +/*DELETE REPLY CONTENT */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Content WHERE ContentId IN ( + Select r.ContentId FROM {databaseOwner}{objectQualifier}activeforums_Replies as r + INNER JOIN {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft on ft.TopicId = r.TopicId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Groups as g on g.ForumGroupId = f.ForumGroupId + WHERE g.ForumGroupId = @ForumGroupId) + +/*DELETE REPLIES */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Replies + WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f ON f.ForumId = ft.ForumID + WHERE f.ForumGroupId = @ForumGroupId) + +/*DELETE TOPIC CONTENT */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Content WHERE ContentId IN ( + Select t.ContentId FROM {databaseOwner}{objectQualifier}activeforums_Topics as t + INNER JOIN {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft on ft.TopicId = t.TopicId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Groups as g on g.ForumGroupId = f.ForumGroupId + WHERE g.ForumGroupId = @ForumGroupId) +/*DELETE TOPICS*/ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft INNER JOIN + {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId + WHERE f.ForumGroupId = @ForumGroupId) + +/*DELETE FORUM TRACKING*/ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Forums_Tracking + WHERE ForumId IN ( + SELECT ForumId FROM {databaseOwner}{objectQualifier}activeforums_Forums WHERE ForumGroupId = @ForumGroupId) +/*DELETE FORUM SETTINGS */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Settings + WHERE ModuleId = @ModuleId AND GroupKey IN ( + SELECT ForumSettingsKey FROM {databaseOwner}{objectQualifier}activeforums_Forums WHERE ForumGroupId = @ForumGroupId) +/*DELETE FORUM SUBSCRIPTIONS */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Subscriptions + WHERE ForumId IN ( + SELECT ForumId FROM {databaseOwner}{objectQualifier}activeforums_Forums WHERE ForumGroupId = @ForumGroupId) +/*DELETE TOPICS CATEGORIES */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Categories + WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId WHERE f.ForumGroupId = @ForumGroupId) +/*DELETE TOPICS TAGS */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Tags + WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId WHERE f.ForumGroupId = @ForumGroupId) +/*DELETE TOPICS TRACKING */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Tracking + WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId WHERE f.ForumGroupId = @ForumGroupId) +/*DELETE TOPICS RATINGS */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Topics_Ratings + WHERE TopicId IN ( + SELECT ft.TopicId FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft INNER JOIN {databaseOwner}{objectQualifier}activeforums_Forums as f on f.ForumId = ft.ForumId WHERE f.ForumGroupId = @ForumGroupId) +/*DELETE FORUMTOPICS TABLE */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_ForumTopics + WHERE ForumId IN ( + SELECT f.ForumId FROM {databaseOwner}{objectQualifier}activeforums_Forums as f WHERE f.ForumGroupId = @ForumGroupId) +/*DELETE FORUMS */ +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Forums WHERE ForumGroupId = @ForumGroupId + + +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Settings WHERE GroupKey = @GroupSettingsKey AND ModuleId = @ModuleId + +DELETE FROM {databaseOwner}{objectQualifier}activeforums_Groups WHERE ForumGroupId = @ForumGroupId AND ModuleId = @ModuleId +GO + +/* activeforums_URL_Search needs to be updated to add categories */ + +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_URL_Search]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_URL_Search] +GO + +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_URL_Search] +@PortalId int, +@Url nvarchar(max) +AS +DECLARE @views TABLE(id int,viewname nvarchar(50)) +INSERT INTO @views (id,viewname) VALUES (1,'unanswered'); +INSERT INTO @views (id,viewname) VALUES (2,'notread'); +INSERT INTO @views (id,viewname) VALUES (3,'mytopics'); +INSERT INTO @views (id,viewname) VALUES (4,'activetopics'); +INSERT INTO @views (id,viewname) VALUES (5,'afprofile'); +INSERT INTO @views (id,viewname) VALUES (6,'mostliked'); +INSERT INTO @views (id,viewname) VALUES (7,'mostreplies'); +INSERT INTO @views (id,viewname) VALUES (8,'afsubscriptions'); +INSERT INTO @views (id,viewname) VALUES (9,'announcements'); +INSERT INTO @views (id,viewname) VALUES (10,'unresolved'); +SELECT TabId, ModuleID, ForumGroupId, ForumId, TopicId, Url,Archived,OtherId,UrlType FROM + ( + SELECT tb.TabID,m.ModuleId, g.ForumGroupId,f.ForumId,t.TopicId, + (CASE WHEN s.SettingValue <> '' THEN s.SettingValue + '/' Else '' END) + g.PrefixURL + '/' + f.PrefixURL + '/' + t.URL + '/' as URL, 0 as Archived,-1 as OtherId,0 as URLType from {databaseOwner}[{objectQualifier}activeforums_Topics] as t + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_ForumTopics] as ft ON ft.TopicId = t.TopicId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Forums] as f ON f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Groups] as g ON g.ForumGroupId = f.ForumGroupId + INNER JOIN {databaseOwner}[{objectQualifier}ModuleSettings] as s ON s.ModuleId = f.ModuleId AND s.SettingName = 'URLBASE' + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = f.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE tb.PortalID = @PortalId AND ISNULL(t.URL,'') <> '' AND ISNULL(f.PrefixURL,'') <> '' + UNION + SELECT tb.TabID,m.ModuleId,g.ForumGroupId,f.ForumId,-1, + (CASE WHEN s.SettingValue <> '' THEN s.SettingValue + '/' Else '' END) + g.PrefixURL + '/' + f.PrefixURL + '/' as URL, 0 as Archived,-1,0 FROM + {databaseOwner}[{objectQualifier}activeforums_Forums] as f + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Groups] as g ON g.ForumGroupId = f.ForumGroupId + INNER JOIN {databaseOwner}[{objectQualifier}ModuleSettings] as s ON s.ModuleId = f.ModuleId AND s.SettingName = 'URLBASE' + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = f.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE tb.PortalID = @PortalId AND ISNULL(f.PrefixURL,'') <> '' + UNION + SELECT tb.TabID,m.ModuleId,g.ForumGroupId,-1,-1, + (CASE WHEN s.SettingValue <> '' THEN s.SettingValue + '/' Else '' END) + g.PrefixURL + '/' as URL, 0 as Archived,-1,0 FROM + {databaseOwner}[{objectQualifier}activeforums_Groups] as g + INNER JOIN {databaseOwner}[{objectQualifier}ModuleSettings] as s ON s.ModuleId = g.ModuleId AND s.SettingName = 'URLBASE' + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = g.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE tb.PortalID = @PortalId AND ISNULL(g.PrefixURL,'') <> '' + UNION + SELECT tb.TabID,m.ModuleId,-1,-1,-1, + (CASE WHEN s.SettingValue <> '' THEN s.SettingValue + '/' Else '' END) as URL, 0 as Archived,-1,0 FROM + {databaseOwner}[{objectQualifier}ModuleSettings] as s + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = s.ModuleId AND s.SettingName = 'URLBASE' + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE tb.PortalID = @PortalId AND s.SettingValue <> '' + UNION + SELECT m.TabID,m.ModuleID,u.ForumGroupId,u.ForumId,u.TopicId, u.URL, 1 as Archived,-1,0 from {databaseOwner}[{objectQualifier}activeforums_URL] as u + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Groups] as g ON u.ForumGroupId = g.ForumGroupId + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = g.ModuleId + WHERE u.PortalId = @PortalId + UNION + SELECT TabId, ModuleId,-1 as ForumGroupId,-1 as ForumId,-1 as TopicId, + (CASE WHEN UrlBase <> '' THEN UrlBase + '/' Else '' END) + UrlOther + '/' + v.viewname + '/' as URL,0 as Archived,v.id,1 from ( + SELECT m.TabId, ss.ModuleId, SettingValue,SettingName FROM {databaseOwner}[{objectQualifier}ModuleSettings] as ss + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = ss.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE (SettingName = 'URLBASE' OR SettingName = 'URLOTHER') AND tb.PortalID = @PortalId + ) as s + PIVOT (MAX(SettingValue) for SettingName in (urlbase,UrlOther)) as pu,@views as v + UNION + SELECT TabId, pu.ModuleId,-1 as ForumGroupId,-1 as ForumId,-1 as TopicId, + (CASE WHEN UrlBase <> '' THEN UrlBase + '/' Else '' END) + URLCATS + '/' + REPLACE(LOWER(t.CategoryName),' ','-') + '/' as URL,0 as Archived,t.CategoryId,2 from ( + SELECT m.TabId, ss.ModuleId, SettingValue,SettingName FROM {databaseOwner}[{objectQualifier}ModuleSettings] as ss + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = ss.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE (SettingName = 'URLBASE' OR SettingName = 'URLCATS') AND tb.PortalID = @PortalId + ) as s + PIVOT (MAX(SettingValue) for SettingName in (urlbase,URLCATS)) as pu + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Categories] as t ON t.ModuleId = pu.ModuleId + UNION + SELECT TabId, pu.ModuleId,-1 as ForumGroupId,-1 as ForumId,-1 as TopicId, + (CASE WHEN UrlBase <> '' THEN UrlBase + '/' Else '' END) + URLTAGS + '/' + REPLACE(LOWER(t.TagName),' ','-') + '/' as URL,0 as Archived,t.TagId,3 from ( + SELECT m.TabId, ss.ModuleId, SettingValue,SettingName FROM {databaseOwner}[{objectQualifier}ModuleSettings] as ss + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = ss.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE (SettingName = 'URLBASE' OR SettingName = 'URLTAGS') AND tb.PortalID = @PortalId + ) as s + PIVOT (MAX(SettingValue) for SettingName in (urlbase,URLTAGS)) as pu + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Tags] as t ON t.ModuleId = pu.ModuleId + UNION + SELECT DISTINCT tb.TabID,m.ModuleId, g.ForumGroupId,f.ForumId ,COALESCE(t.TopicId, r.TopicId) AS TopicId, + (CASE WHEN s1.SettingValue <> '' THEN s1.SettingValue + '/' Else '' END) + g.PrefixURL + '/' + f.PrefixURL + '/' + ISNULL(ISNULL(rt.URL,t.URL),'') + '/' + COALESCE('likes',s2.SettingValue) + '/' + LTRIM(STR(c.ContentId)) + '/' as URL, 0 as Archived,c.ContentId AS OtherId,4 as URLType + FROM {databaseOwner}[{objectQualifier}activeforums_Content] as c + LEFT OUTER JOIN {databaseOwner}[{objectQualifier}activeforums_Topics] as t ON t.ContentId = c.ContentId + LEFT OUTER JOIN {databaseOwner}[{objectQualifier}activeforums_Replies] as r ON r.ContentId = c.ContentId + LEFT OUTER JOIN {databaseOwner}[{objectQualifier}activeforums_Topics] as rt ON rt.TopicId = r.TopicId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_ForumTopics] as ft ON ft.TopicId = COALESCE(t.TopicId, r.TopicId) + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Forums] as f ON f.ForumId = ft.ForumId + INNER JOIN {databaseOwner}[{objectQualifier}activeforums_Groups] as g ON g.ForumGroupId = f.ForumGroupId + INNER JOIN {databaseOwner}[{objectQualifier}ModuleSettings] as s1 ON s1.ModuleId = f.ModuleId AND s1.SettingName = 'URLBASE' + LEFT OUTER JOIN {databaseOwner}[{objectQualifier}ModuleSettings] as s2 ON s2.ModuleId = f.ModuleId AND s2.SettingName = 'URLLIKES' + INNER JOIN {databaseOwner}[{objectQualifier}TabModules] as m ON m.ModuleID = f.ModuleId + INNER JOIN {databaseOwner}[{objectQualifier}Tabs] as tb ON tb.TabId = m.TabID + WHERE c.IsDeleted = 0 AND tb.PortalID = @PortalId AND ISNULL(ISNULL(rt.URL,t.URL),'') <> '' AND ISNULL(f.PrefixURL,'') <> '' + + ) as urls + WHERE LOWER(urls.URL) = @URL +GO + +/*activeforums_Topics_GetCategories*/ +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_GetCategories]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) +DROP FUNCTION {databaseOwner}[{objectQualifier}activeforums_Topics_GetCategories] +GO +CREATE FUNCTION {databaseOwner}[{objectQualifier}activeforums_Topics_GetCategories](@TopicId int) +RETURNS nvarchar(255) +AS +BEGIN +Declare category_curs cursor for +SELECT T.CategoryName +FROM {databaseOwner}{objectQualifier}activeforums_Categories AS T INNER JOIN + {databaseOwner}{objectQualifier}activeforums_Topics_Categories AS C ON T.CategoryId = C.CategoryId +WHERE (C.TopicId = @TopicId) + +Declare @Categories nvarchar(255) +Declare @CategoriesOut nvarchar(255) +Set @CategoriesOut = '' +open category_curs + +fetch next from category_curs into @Categories + +WHILE (@@fetch_status = 0) +BEGIN + Set @CategoriesOut = @CategoriesOut + @Categories + '|' + + fetch next from category_curs into @Categories +END + + +close category_curs +deallocate category_curs +IF LEN(@CategoriesOut) > 1 + SET @CategoriesOut = SUBSTRING(@CategoriesOut, 1, LEN(@CategoriesOut)-1) +RETURN @CategoriesOut +END + +GO + +/*activeforums_UI_TagCloud*/ +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_UI_TagCloud]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_UI_TagCloud] +GO +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_UI_TagCloud] +@PortalId int, +@ModuleId int, +@ForumIds nvarchar(max), +@Rows int +AS +SET ROWCOUNT @Rows +DECLARE @factor int +SET @factor = (@Rows/3) +DECLARE @tags TABLE(id int IDENTITY,tagid int,tagname nvarchar(100), items int) +INSERT INTO @tags (tagid, tagname, items) +SELECT tagId, tagname, items FROM (SELECT DISTINCT tg.TagId, tg.TagName,tg.Items + FROM {databaseOwner}{objectQualifier}activeforums_Tags as tg + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Topics_Tags as tt ON tt.TagId = tg.TagId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_ForumTopics as ft ON ft.TopicId = tt.TopicId + INNER JOIN {databaseOwner}{objectQualifier}activeforums_Functions_Split(@ForumIds,';') as f ON f.id = ft.ForumId + WHERE TagName <> '' AND PortalId = @PortalId AND ModuleId = @ModuleId + ) as tg + ORDER BY Items DESC +SELECT id, tagid, tagname, items,@factor, CASE + WHEN id < @factor THEN 3 + WHEN id >=@factor AND id <(@factor*2) THEN 2 + WHEN id >=(@factor*2) THEN 1 END as Priority from @tags +GO + + +/* activeforums_Tags_List */ +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags_List]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Tags_List] +GO +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Tags_List] + @PortalId int, + @ModuleId int, + @PageIndex int, + @PageSize int, + @Sort varchar(4) = "DESC", + @SortColumn varchar(50) = "TagName" +AS +SELECT COUNT(TagId) FROM {databaseOwner}{objectQualifier}activeforums_Tags +WHERE Portalid = @PortalId AND ModuleId = @ModuleId +SELECT * FROM {databaseOwner}{objectQualifier}activeforums_Tags WHERE Portalid = @PortalId AND ModuleId = @ModuleId +ORDER BY TagName +GO + +/* activeforums_Categories_List */ +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Categories_List]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Categories_List] +GO +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Categories_List] + @PortalId int, + @ModuleId int, + @PageIndex int, + @PageSize int, + @Sort varchar(4) = "DESC", + @SortColumn varchar(50) = "CategoryName", + @ForumId int, + @ForumGroupId int +AS +SELECT COUNT(CategoryId) FROM {databaseOwner}{objectQualifier}activeforums_Categories +WHERE Portalid = @PortalId AND + ModuleId = @ModuleId AND + ( + (@ForumId = -1 AND @ForumGroupId = -1) + OR + (@ForumId > 0 AND ForumId = @ForumId) + OR + (@ForumGroupId > 0 AND ForumGroupId = @ForumGroupId) + OR + (ForumId = -1 AND ForumGroupId = -1) + ) +SELECT * FROM {databaseOwner}{objectQualifier}activeforums_Categories WHERE Portalid = @PortalId AND ModuleId = @ModuleId + AND + ( + (@ForumId = -1 AND @ForumGroupId = -1) + OR + (@ForumId > 0 AND ForumId = @ForumId) + OR + (@ForumGroupId > 0 AND ForumGroupId = @ForumGroupId) + OR + (ForumId = -1 AND ForumGroupId = -1) + ) +ORDER BY CategoryName +GO + +/* activeforums_Tags_GetByName */ +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags_GetByName]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Tags_GetByName] +GO +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Tags_GetByName] + @PortalId int, + @ModuleId int, + @TagName nvarchar(50) +AS +SELECT TOP 1 TagId FROM {databaseOwner}{objectQualifier}activeforums_Tags WHERE PortalId = @PortalId AND ModuleId = @ModuleId AND LTRIM(RTRIM(LOWER(TagName))) = @TagName +GO + + +/* activeforums_Tags_Save */ +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags_Save]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Tags_Save] +GO +CREATE PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Tags_Save] + @PortalId int, + @ModuleId int, + @TagId int, + @TagName nvarchar(255), + @Items int, + @TopicId int = -1 +AS +BEGIN + IF EXISTS(SELECT * FROM {databaseOwner}{objectQualifier}activeforums_Tags WHERE (PortalId = @PortalId AND ModuleId = @ModuleId) AND (TagId = @TagId OR TagName = @TagName)) + BEGIN + IF @TagId <= 0 + SELECT @TagId = TagId FROM {databaseOwner}{objectQualifier}activeforums_Tags WHERE (PortalId = @PortalId AND ModuleId = @ModuleId AND UPPER(RTRIM(LTRIM(TagName))) = UPPER(RTRIM(LTRIM(@TagName)))) + SET @Items = (SELECT COUNT(TopicId) FROM {databaseOwner}{objectQualifier}activeforums_Topics_Tags WHERE TagId = @TagId) + UPDATE {databaseOwner}{objectQualifier}activeforums_Tags + SET TagName = @TagName, Items = @Items + 1 + WHERE PortalId = @PortalId AND ModuleId = @ModuleId AND TagId = @TagId + END + ELSE + BEGIN + INSERT INTO {databaseOwner}{objectQualifier}activeforums_Tags + (PortalId, ModuleId, TagName, Items) + Values + (@PortalId, @ModuleId, @TagName, @Items) + SET @TagId = SCOPE_IDENTITY() + END +END +IF @TopicId > 0 + IF NOT EXISTS(SELECT TopicId FROM {databaseOwner}{objectQualifier}activeforums_Topics_Tags WHERE TopicId = @TopicId AND TagId = @TagId) + INSERT INTO {databaseOwner}{objectQualifier}activeforums_Topics_Tags + (TopicId, TagId)VALUES(@TopicId, @TagId) +GO + +/* activeforums_Tags_AddTopicToCategory */ +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags_AddTopicToCategory]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Tags_AddTopicToCategory] +GO + +/* activeforums_Tags_DeleteTopicToCategory */ +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags_DeleteTopicToCategory]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Tags_DeleteTopicToCategory] +GO +/* issue 1411 - end - separate categories from tags */ /* --------------------- */ diff --git a/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider b/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider index e33a350f9..8a432df1d 100644 --- a/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider +++ b/Dnn.CommunityForums/sql/Uninstall.SqlDataProvider @@ -102,6 +102,9 @@ GO IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Attachments_SaveFromTemp]') AND type in (N'P', N'PC')) DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Attachments_SaveFromTemp] GO +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Categories_List]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Categories_List] +GO IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_DashBoard_Stats]') AND type in (N'P', N'PC')) DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_DashBoard_Stats] GO @@ -273,6 +276,12 @@ GO IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Templates_Save]') AND type in (N'P', N'PC')) DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Templates_Save] GO +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_AddRating]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Topics_AddRating] +GO +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_GetRating]') AND type in (N'P', N'PC')) +DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Topics_GetRating] +GO IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_Delete]') AND type in (N'P', N'PC')) DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Topics_Delete] GO @@ -372,12 +381,6 @@ GO IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_TopicIdByURL]') AND type in (N'P', N'PC')) DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_TopicIdByURL] GO -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags_AddTopicToCategory]') AND type in (N'P', N'PC')) -DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Tags_AddTopicToCategory] -GO -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Tags_DeleteTopicToCategory]') AND type in (N'P', N'PC')) -DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Tags_DeleteTopicToCategory] -GO IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Properties_List]') AND type in (N'P', N'PC')) DROP PROCEDURE {databaseOwner}[{objectQualifier}activeforums_Properties_List] GO @@ -751,6 +754,12 @@ GO IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics]') AND type in (N'U')) DROP TABLE {databaseOwner}[{objectQualifier}activeforums_Topics] GO +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_Categories]') AND type in (N'U')) +DROP TABLE {databaseOwner}[{objectQualifier}activeforums_Topics_Categories] +GO +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Categories]') AND type in (N'U')) +DROP TABLE {databaseOwner}[{objectQualifier}activeforums_Categories] +GO IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}activeforums_Topics_Ratings]') AND type in (N'U')) DROP TABLE {databaseOwner}[{objectQualifier}activeforums_Topics_Ratings] GO diff --git a/Dnn.CommunityForums/themes/community-bootstrap/templates/TopicView.ascx b/Dnn.CommunityForums/themes/community-bootstrap/templates/TopicView.ascx index ddfe7956f..27dd0c2e3 100644 --- a/Dnn.CommunityForums/themes/community-bootstrap/templates/TopicView.ascx +++ b/Dnn.CommunityForums/themes/community-bootstrap/templates/TopicView.ascx @@ -131,10 +131,6 @@
[ATTACHMENTS]
-
diff --git a/Dnn.CommunityForums/themes/community-default/templates/TopicView.ascx b/Dnn.CommunityForums/themes/community-default/templates/TopicView.ascx index d826b64ff..770957062 100644 --- a/Dnn.CommunityForums/themes/community-default/templates/TopicView.ascx +++ b/Dnn.CommunityForums/themes/community-default/templates/TopicView.ascx @@ -144,10 +144,6 @@
[ATTACHMENTS]
-
diff --git a/DnnCommunityForums.sln b/DnnCommunityForums.sln index 364b7f0ed..176855f43 100644 --- a/DnnCommunityForums.sln +++ b/DnnCommunityForums.sln @@ -36,7 +36,6 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - RESX_AutoApplyExistingTranslations = False SolutionGuid = {DACD6BD0-773E-403D-A41B-38957EE5F01D} EndGlobalSection EndGlobal diff --git a/ResXManager.config.xml b/ResXManager.config.xml new file mode 100644 index 000000000..18e3c612b --- /dev/null +++ b/ResXManager.config.xml @@ -0,0 +1,4 @@ + + + False + \ No newline at end of file From ca2c68f258135586fa9281c2ae28e7435a5e9c53 Mon Sep 17 00:00:00 2001 From: John Henley Date: Wed, 20 Aug 2025 14:44:16 +0000 Subject: [PATCH 17/40] FIX: Incorrect pager links when not using friendly URLs --- .../CustomControls/UserControls/TopicView.cs | 12 ++++++++++-- .../CustomControls/UserControls/TopicsView.cs | 11 +++++++++-- Dnn.CommunityForums/components/Helpers/URL.cs | 2 +- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs b/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs index b3931c8a4..92e5d1f8c 100644 --- a/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs +++ b/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs @@ -1221,7 +1221,11 @@ private void BuildPager() pager1.View = Views.Topic; pager1.TopicId = this.topic.TopicId; pager1.PageMode = PagerNav.Mode.Links; - pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + if (this.MainSettings.URLRewriteEnabled) + { + pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + } + pager1.Params = @params.ToArray(); } @@ -1237,7 +1241,11 @@ private void BuildPager() pager2.View = Views.Topic; pager2.TopicId = this.topic.TopicId; pager2.PageMode = PagerNav.Mode.Links; - pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + if (this.MainSettings.URLRewriteEnabled) + { + pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + } + pager2.Params = @params.ToArray(); } } diff --git a/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs b/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs index d09d9e7f5..e30c36481 100644 --- a/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs +++ b/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs @@ -657,7 +657,11 @@ private void BuildPager() intPages = Convert.ToInt32(System.Math.Ceiling(this.topicRowCount / (double)this.pageSize)); pager1.PageCount = intPages; pager1.PageMode = PagerNav.Mode.Links; - pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + if (this.MainSettings.URLRewriteEnabled) + { + pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + } + pager1.CurrentPage = this.PageId; pager1.TabID = Convert.ToInt32(this.Request.Params["TabId"]); pager1.ForumID = this.ForumId; @@ -679,7 +683,10 @@ private void BuildPager() if (pager2 != null) { pager2.PageMode = Modules.ActiveForums.Controls.PagerNav.Mode.Links; - pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + if (this.MainSettings.URLRewriteEnabled) + { + pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + } pager2.UseShortUrls = this.MainSettings.UseShortUrls; pager2.PageCount = intPages; pager2.CurrentPage = this.PageId; diff --git a/Dnn.CommunityForums/components/Helpers/URL.cs b/Dnn.CommunityForums/components/Helpers/URL.cs index a14f60b68..cccf01b6a 100644 --- a/Dnn.CommunityForums/components/Helpers/URL.cs +++ b/Dnn.CommunityForums/components/Helpers/URL.cs @@ -32,7 +32,7 @@ public static string ForumLink(int tabId, DotNetNuke.Modules.ActiveForums.Entiti if (string.IsNullOrWhiteSpace(fi.PrefixURL) || !mainSettings.URLRewriteEnabled) { - sURL = Utilities.NavigateURL(tabId, string.Empty, ParamKeys.ForumId + "=" + fi.ForumID); + sURL = Utilities.NavigateURL(tabId, string.Empty, ParamKeys.ForumId + "=" + fi.ForumID) + "/"; } else { From 22ed3d72cdf5c301562bb506786083d1a3b0fdc0 Mon Sep 17 00:00:00 2001 From: John Henley Date: Wed, 20 Aug 2025 14:44:16 +0000 Subject: [PATCH 18/40] FIX: Incorrect pager links when not using friendly URLs --- .../CustomControls/UserControls/TopicView.cs | 12 ++++- .../CustomControls/UserControls/TopicsView.cs | 11 +++- Dnn.CommunityForums/ReleaseNotes.txt | 52 ++++++++----------- Dnn.CommunityForums/components/Helpers/URL.cs | 2 +- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs b/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs index b3931c8a4..92e5d1f8c 100644 --- a/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs +++ b/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs @@ -1221,7 +1221,11 @@ private void BuildPager() pager1.View = Views.Topic; pager1.TopicId = this.topic.TopicId; pager1.PageMode = PagerNav.Mode.Links; - pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + if (this.MainSettings.URLRewriteEnabled) + { + pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + } + pager1.Params = @params.ToArray(); } @@ -1237,7 +1241,11 @@ private void BuildPager() pager2.View = Views.Topic; pager2.TopicId = this.topic.TopicId; pager2.PageMode = PagerNav.Mode.Links; - pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + if (this.MainSettings.URLRewriteEnabled) + { + pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + } + pager2.Params = @params.ToArray(); } } diff --git a/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs b/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs index d09d9e7f5..e30c36481 100644 --- a/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs +++ b/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs @@ -657,7 +657,11 @@ private void BuildPager() intPages = Convert.ToInt32(System.Math.Ceiling(this.topicRowCount / (double)this.pageSize)); pager1.PageCount = intPages; pager1.PageMode = PagerNav.Mode.Links; - pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + if (this.MainSettings.URLRewriteEnabled) + { + pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + } + pager1.CurrentPage = this.PageId; pager1.TabID = Convert.ToInt32(this.Request.Params["TabId"]); pager1.ForumID = this.ForumId; @@ -679,7 +683,10 @@ private void BuildPager() if (pager2 != null) { pager2.PageMode = Modules.ActiveForums.Controls.PagerNav.Mode.Links; - pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + if (this.MainSettings.URLRewriteEnabled) + { + pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + } pager2.UseShortUrls = this.MainSettings.UseShortUrls; pager2.PageCount = intPages; pager2.CurrentPage = this.PageId; diff --git a/Dnn.CommunityForums/ReleaseNotes.txt b/Dnn.CommunityForums/ReleaseNotes.txt index c822f81fa..8ca4f6a36 100644 --- a/Dnn.CommunityForums/ReleaseNotes.txt +++ b/Dnn.CommunityForums/ReleaseNotes.txt @@ -15,14 +15,15 @@
-

Important Upgrade Notes

+

Important Upgrade Note

- VERY IMPORTANT:The MINIMUM DNN Platform version for this release is now DNN 9.11. + VERY IMPORTANT: The MINIMUM DNN Platform version for this release is now DNN 9.11. +

- +

DNN Community Forums Release Notes

-
-

- 09.00.02 + 09.01.00

- . - .

What's to follow are all of the relevant updates that have occurred during the development cycle of this release.

+-->

New Features & Enhancements

    +
  • NEW: Adds Recycle Bin to be able to restore (soft-)deleted topics and replies (Issue 515)
  • +
  • NEW: Adds an avatar injection service to populate avatars (currently using Gravatar) for forums users who haven't set up their own DNN profile picture/avatar. (Issue 349)
  • None at this time.
  • - +-->

Tasks / Development Updates (and Technical Debt)


diff --git a/Dnn.CommunityForums/components/Helpers/URL.cs b/Dnn.CommunityForums/components/Helpers/URL.cs index a14f60b68..cccf01b6a 100644 --- a/Dnn.CommunityForums/components/Helpers/URL.cs +++ b/Dnn.CommunityForums/components/Helpers/URL.cs @@ -32,7 +32,7 @@ public static string ForumLink(int tabId, DotNetNuke.Modules.ActiveForums.Entiti if (string.IsNullOrWhiteSpace(fi.PrefixURL) || !mainSettings.URLRewriteEnabled) { - sURL = Utilities.NavigateURL(tabId, string.Empty, ParamKeys.ForumId + "=" + fi.ForumID); + sURL = Utilities.NavigateURL(tabId, string.Empty, ParamKeys.ForumId + "=" + fi.ForumID) + "/"; } else { From 95f3509244cda5e48fa4220b08b7e294dcf36700 Mon Sep 17 00:00:00 2001 From: John Henley Date: Wed, 20 Aug 2025 14:44:16 +0000 Subject: [PATCH 19/40] FIX: Incorrect pager links when not using friendly URLs --- .../CustomControls/UserControls/TopicView.cs | 12 ++++++++++-- .../CustomControls/UserControls/TopicsView.cs | 11 +++++++++-- Dnn.CommunityForums/ReleaseNotes.txt | 5 +++-- Dnn.CommunityForums/components/Helpers/URL.cs | 2 +- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs b/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs index b3931c8a4..92e5d1f8c 100644 --- a/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs +++ b/Dnn.CommunityForums/CustomControls/UserControls/TopicView.cs @@ -1221,7 +1221,11 @@ private void BuildPager() pager1.View = Views.Topic; pager1.TopicId = this.topic.TopicId; pager1.PageMode = PagerNav.Mode.Links; - pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + if (this.MainSettings.URLRewriteEnabled) + { + pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + } + pager1.Params = @params.ToArray(); } @@ -1237,7 +1241,11 @@ private void BuildPager() pager2.View = Views.Topic; pager2.TopicId = this.topic.TopicId; pager2.PageMode = PagerNav.Mode.Links; - pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + if (this.MainSettings.URLRewriteEnabled) + { + pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo) + this.topic.TopicUrl; + } + pager2.Params = @params.ToArray(); } } diff --git a/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs b/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs index d09d9e7f5..e30c36481 100644 --- a/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs +++ b/Dnn.CommunityForums/CustomControls/UserControls/TopicsView.cs @@ -657,7 +657,11 @@ private void BuildPager() intPages = Convert.ToInt32(System.Math.Ceiling(this.topicRowCount / (double)this.pageSize)); pager1.PageCount = intPages; pager1.PageMode = PagerNav.Mode.Links; - pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + if (this.MainSettings.URLRewriteEnabled) + { + pager1.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + } + pager1.CurrentPage = this.PageId; pager1.TabID = Convert.ToInt32(this.Request.Params["TabId"]); pager1.ForumID = this.ForumId; @@ -679,7 +683,10 @@ private void BuildPager() if (pager2 != null) { pager2.PageMode = Modules.ActiveForums.Controls.PagerNav.Mode.Links; - pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + if (this.MainSettings.URLRewriteEnabled) + { + pager2.BaseURL = URL.ForumLink(this.TabId, this.ForumInfo); + } pager2.UseShortUrls = this.MainSettings.UseShortUrls; pager2.PageCount = intPages; pager2.CurrentPage = this.PageId; diff --git a/Dnn.CommunityForums/ReleaseNotes.txt b/Dnn.CommunityForums/ReleaseNotes.txt index a0a0c3380..8ca4f6a36 100644 --- a/Dnn.CommunityForums/ReleaseNotes.txt +++ b/Dnn.CommunityForums/ReleaseNotes.txt @@ -78,9 +78,10 @@

Bug Fixes

    -
  • None at this time.
  • +
  • FIXED: Incorrect URLs in navigation pager when not using friendly URLs (Issue 1537)
  • +
diff --git a/Dnn.CommunityForums/components/Helpers/URL.cs b/Dnn.CommunityForums/components/Helpers/URL.cs index a14f60b68..cccf01b6a 100644 --- a/Dnn.CommunityForums/components/Helpers/URL.cs +++ b/Dnn.CommunityForums/components/Helpers/URL.cs @@ -32,7 +32,7 @@ public static string ForumLink(int tabId, DotNetNuke.Modules.ActiveForums.Entiti if (string.IsNullOrWhiteSpace(fi.PrefixURL) || !mainSettings.URLRewriteEnabled) { - sURL = Utilities.NavigateURL(tabId, string.Empty, ParamKeys.ForumId + "=" + fi.ForumID); + sURL = Utilities.NavigateURL(tabId, string.Empty, ParamKeys.ForumId + "=" + fi.ForumID) + "/"; } else { From 202627358599ef1e598f29fef7c4ec974c607fb4 Mon Sep 17 00:00:00 2001 From: John Henley Date: Thu, 21 Aug 2025 21:22:53 +0000 Subject: [PATCH 20/40] TASK: Release Prep 09.01.00 --- Dnn.CommunityForums/DnnCommunityForums.dnn | 10 +++++++--- .../DnnCommunityForums_Symbols.dnn | 1 - Dnn.CommunityForums/ReleaseNotes.txt | 18 +++++++----------- .../controls/af_recycle_bin.ascx.cs | 1 + 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Dnn.CommunityForums/DnnCommunityForums.dnn b/Dnn.CommunityForums/DnnCommunityForums.dnn index 99bf6de86..37076b790 100644 --- a/Dnn.CommunityForums/DnnCommunityForums.dnn +++ b/Dnn.CommunityForums/DnnCommunityForums.dnn @@ -75,7 +75,7 @@ DotNetNuke.Modules.ActiveForums.TopicsController, DotNetNuke.Modules.ActiveForums [DESKTOPMODULEID] - 07.00.07,07.00.11,07.00.12,08.00.00,08.01.00,08.02.00,08.02.02,08.02.03,08.02.04,08.02.08,09.00.00 + 07.00.07,07.00.11,07.00.12,08.00.00,08.01.00,08.02.00,08.02.02,08.02.03,08.02.04,08.02.08,09.00.00,09.01.00 @@ -424,6 +424,11 @@ 09.00.00.SqlDataProvider 09.00.00 +
+
[RESX:TagName]
+
+
[RESX:TagItems]
+
+
 
+