diff --git a/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.fr-FR.resx b/Dnn.CommunityForums/App_LocalResources/ControlPanel.ascx.fr-FR.resx index 122f1320..b6713f6d 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 86ca1f0e..9765d40d 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 d2ab8540..6d67a8f7 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 6c1de919..b0cf6064 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 ac6943e3..1fc250e0 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 2b95b086..c4705559 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 723a9271..dee29ae8 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 51ec10b8..0b811fd7 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 9130eb46..469fe65e 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 39f80040..9706723c 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 9f7c0df0..285301cf 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 080a5e47..578a8edd 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 265add62..86b2ee4c 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.de-DE.resx @@ -1841,4 +1841,7 @@ Von Ausnahme: Ersetzen des Tokens für Entität: {0}-ID: {1} Tokeneigenschaft: {2} Format: {3} + + 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 f24d2d70..1e8242e3 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.es-ES.resx @@ -1840,4 +1840,7 @@ De Excepción que reemplaza el token en la entidad: {0} id: {1} propiedad del token: {2} formato: {3} + + 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 3861c0af..44b23e19 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.fr-FR.resx @@ -1837,4 +1837,7 @@ De, Exception remplaçant le jeton sur l’entité : {0} id : {1} propriété du jeton : {2} format : {3} + + 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 c016aa0b..074c1175 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.it-IT.resx @@ -1840,4 +1840,7 @@ Da Eccezione che sostituisce il token sull'entità: {0} id: {1} proprietà token: {2} formato: {3} + + 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 9ef561b4..bdef5576 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.nl-NL.resx @@ -1876,4 +1876,7 @@ Van, Uitzondering ter vervanging van token op entiteit: {0} id: {1} eigenschap token: {2} indeling: {3} + + 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 57e6e87d..b6f7b73f 100644 --- a/Dnn.CommunityForums/App_LocalResources/SharedResources.resx +++ b/Dnn.CommunityForums/App_LocalResources/SharedResources.resx @@ -1837,4 +1837,7 @@ From, Exception replacing token on entity: {0} id: {1} token property: {2} format: {3} + + 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 56f56cbb..c55d2eef 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 9f04cea8..f5782761 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 2cbb6e29..ddd21640 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 d9a36ac8..ed3e3f51 100644 --- a/Dnn.CommunityForums/ForumSettings.ascx +++ b/Dnn.CommunityForums/ForumSettings.ascx @@ -112,6 +112,13 @@ +
+ + + + + +
diff --git a/Dnn.CommunityForums/ForumSettings.ascx.cs b/Dnn.CommunityForums/ForumSettings.ascx.cs index 13f63ea7..4f89d095 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 13b1fc7d..46c89908 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 00000000..c878c823 --- /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.IsDeleted && 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($"Processed {intQueueCount} avatar refresh requests for {module.ModuleTitle} on portal {portal.PortalName}. "); + } + } + } + } + } + } + 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.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(); + } + 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.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); + 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 00000000..cbcf84ae --- /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 "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"; + } + } +} diff --git a/Dnn.CommunityForums/Services/Avatars/IAvatarProvider.cs b/Dnn.CommunityForums/Services/Avatars/IAvatarProvider.cs new file mode 100644 index 00000000..21c37c2c --- /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/ForumsConfig.cs b/Dnn.CommunityForums/class/ForumsConfig.cs index 5894c39d..4fb5a005 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); } diff --git a/Dnn.CommunityForums/class/Globals.cs b/Dnn.CommunityForums/class/Globals.cs index c1b3562e..3464c142 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 4d78b89c..24a6b3c4 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 c389f6dd..302c6773 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 728e5e6c..4fa3890f 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 35f07d7e..7b520ea9 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 d11b0591..b7f8ac5a 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 03675235..09ddf467 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 00000000..05bf8c9d --- /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'IX_{objectQualifier}activeforums_UserProfiles_Opt5') +DROP INDEX [IX_{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 4bafb943..9b7b3725 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'IX_{objectQualifier}activeforums_UserProfiles_Opt5') +DROP INDEX [IX_{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 74244c5f..e29eae25 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 857134e2..909d721c 100644 --- a/Dnn.CommunityForumsTests/class/UtilitiesTests.cs +++ b/Dnn.CommunityForumsTests/class/UtilitiesTests.cs @@ -445,5 +445,19 @@ public void DecodeBrackets_WithPartialEncodedBrackets_OnlyDecodesComplete() // Assert Assert.That(result, Is.EqualTo(expected)); } + + [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 176855f4..364b7f0e 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