diff --git a/OpenContent/BuildScripts/ModulePackage.targets b/OpenContent/BuildScripts/ModulePackage.targets index 9af64e1c..38fb28ef 100644 --- a/OpenContent/BuildScripts/ModulePackage.targets +++ b/OpenContent/BuildScripts/ModulePackage.targets @@ -8,6 +8,7 @@ $(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs + $(MSBuildProjectDirectory)\..\appveyor.yml @@ -23,8 +24,9 @@ - - + + + diff --git a/OpenContent/Components/Datasource/DefaultDataItem.cs b/OpenContent/Components/Datasource/DefaultDataItem.cs index af7aac3b..c058e3da 100644 --- a/OpenContent/Components/Datasource/DefaultDataItem.cs +++ b/OpenContent/Components/Datasource/DefaultDataItem.cs @@ -5,14 +5,29 @@ namespace Satrabel.OpenContent.Components.Datasource { public class DefaultDataItem : IDataItem { + [Obsolete("Please use constructor with parameters 12/10/2021")] public DefaultDataItem() { } + public DefaultDataItem(string id) + { + Id = id; + Key = id; + } + + public DefaultDataItem(string id, string key) + { + Id = id; + Key = key; + } public DefaultDataItem(JToken json) { + Id = null; + Key = null; Data = json; } + public string Id { get; set; } public string Key { get; set; } public string Collection { get; set; } @@ -23,6 +38,5 @@ public DefaultDataItem(JToken json) public int LastModifiedByUserId { get; set; } public DateTime LastModifiedOnDate { get; set; } public object Item { get; set; } - } } \ No newline at end of file diff --git a/OpenContent/Components/Datasource/DnnPortalSettingsDataSource.cs b/OpenContent/Components/Datasource/DnnPortalSettingsDataSource.cs index 8e9feaef..2e2d13ff 100644 --- a/OpenContent/Components/Datasource/DnnPortalSettingsDataSource.cs +++ b/OpenContent/Components/Datasource/DnnPortalSettingsDataSource.cs @@ -33,9 +33,8 @@ public override IDataItem Get(DataSourceContext context, string id) } private static IDataItem ToData(PortalSettingInfoBase setting) { - var item = new DefaultDataItem() + var item = new DefaultDataItem(setting.Id()) { - Id = setting.Id(), Title = $"{setting.SettingName}", Data = JObject.FromObject(new { diff --git a/OpenContent/Components/Datasource/DnnTabsDataSource.cs b/OpenContent/Components/Datasource/DnnTabsDataSource.cs index efd00b77..af3262c1 100644 --- a/OpenContent/Components/Datasource/DnnTabsDataSource.cs +++ b/OpenContent/Components/Datasource/DnnTabsDataSource.cs @@ -41,9 +41,8 @@ public override IDataItems GetAll(DataSourceContext context, Select selectQuery) var dataList = new List(); foreach (var tab in tabs) { - var item = new DefaultDataItem() + var item = new DefaultDataItem(tab.TabID.ToString()) { - Id = tab.TabID.ToString(), Title = tab.TabName, Data = JObject.FromObject(new { diff --git a/OpenContent/Components/Datasource/DnnUsersDataSource.cs b/OpenContent/Components/Datasource/DnnUsersDataSource.cs index 347af26a..fa78126c 100644 --- a/OpenContent/Components/Datasource/DnnUsersDataSource.cs +++ b/OpenContent/Components/Datasource/DnnUsersDataSource.cs @@ -40,9 +40,8 @@ public override IDataItem Get(DataSourceContext context, string id) } private static IDataItem ToData(UserInfo user) { - var item = new DefaultDataItem() + var item = new DefaultDataItem(user.UserID.ToString()) { - Id = user.UserID.ToString(), Title = user.DisplayName, Data = JObject.FromObject(new { diff --git a/OpenContent/Components/Datasource/OpenContentDataSource.cs b/OpenContent/Components/Datasource/OpenContentDataSource.cs index 48a8b72b..d399ec6c 100644 --- a/OpenContent/Components/Datasource/OpenContentDataSource.cs +++ b/OpenContent/Components/Datasource/OpenContentDataSource.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; +using DotNetNuke.Entities.Modules; +using DotNetNuke.Entities.Portals; using Satrabel.OpenContent.Components.Datasource.Search; using Satrabel.OpenContent.Components.Logging; using Satrabel.OpenContent.Components.Form; @@ -165,7 +167,7 @@ public virtual IDataItem GetData(DataSourceContext context, string scope, string var json = dc.GetData(scopeStorage, key); if (json != null) { - var dataItem = new DefaultDataItem + var dataItem = new DefaultDataItem("") { Data = json.Json.ToJObject("GetContent " + scope + "/" + key), CreatedByUserId = json.CreatedByUserId, @@ -304,8 +306,9 @@ public virtual void Update(DataSourceContext context, IDataItem item, JToken dat ctrl.UpdateContent(content); if (context.Index) { + var module = OpenContentModuleConfig.Create(ModuleController.Instance.GetModule(context.ModuleId, -1, false), new PortalSettings(context.PortalId)); var indexConfig = OpenContentUtils.GetIndexConfig(new FolderUri(context.TemplateFolder), context.Collection); - content.HydrateDefaultFields(indexConfig); + content.HydrateDefaultFields(indexConfig, module.Settings?.Manifest?.UsePublishTime ?? false); LuceneController.Instance.Update(content, indexConfig); LuceneController.Instance.Commit(); } @@ -353,7 +356,7 @@ public virtual JToken Action(DataSourceContext context, string action, IDataItem { if (action == "FormSubmit") { - if (data["form"]["approvalEnabled"] != null && data["form"]["approvalEnabled"].Value() == true ) + if (data["form"]["approvalEnabled"] != null && data["form"]["approvalEnabled"].Value() == true) { data["form"]["approved"] = false; } @@ -373,7 +376,7 @@ public virtual JToken Action(DataSourceContext context, string action, IDataItem ctrl.AddContent(content); //Index the content item - + if (context.Index) { var indexConfig = OpenContentUtils.GetIndexConfig(new FolderUri(context.TemplateFolder), "Submissions"); @@ -463,10 +466,8 @@ private static int GetTabId(DataSourceContext context) private static DefaultDataItem CreateDefaultDataItem(OpenContentInfo content) { - return new DefaultDataItem + return new DefaultDataItem(content.Id) { - Id = content.Id, - Key= content.Key, Collection = content.Collection, Title = content.Title, Data = content.JsonAsJToken, diff --git a/OpenContent/Components/Datasource/RestApiDataSource.cs b/OpenContent/Components/Datasource/RestApiDataSource.cs new file mode 100644 index 00000000..248ba274 --- /dev/null +++ b/OpenContent/Components/Datasource/RestApiDataSource.cs @@ -0,0 +1,260 @@ +using DotNetNuke.Services.Mail; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Satrabel.OpenContent.Components; +using Satrabel.OpenContent.Components.Datasource; +using Satrabel.OpenContent.Components.Datasource.Search; +using Satrabel.OpenContent.Components.Form; +using Satrabel.OpenContent.Components.Handlebars; +using Satrabel.OpenContent.Components.Logging; +using Satrabel.OpenContent.Components.Lucene; +using Satrabel.OpenContent.Components.Lucene.Config; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Mail; +using System.Web; + +namespace Satrabel.OpenContent.Components.Datasource +{ + public class RestApiDataSource : OpenContentDataSource + { + + public override string Name + { + get + { + return "Satrabel.RestApi"; + } + } + public override IDataItems GetAll(DataSourceContext context) + { + JArray items = new JArray(); + + var url = context.Config["listUrl"].ToString(); + + using (var client = new HttpClient()) + { + var response = client.GetAsync(url).GetAwaiter().GetResult(); ; + response.EnsureSuccessStatusCode(); + var responseBody = response.Content.ReadAsStringAsync(); + var content = responseBody.GetAwaiter().GetResult(); + items = JArray.Parse(content); + } + var dataList = items + .Select(content => CreateDefaultDataItem(content)); + + return new DefaultDataItems() + { + Items = dataList, + Total = dataList.Count() + }; + } + + public override IDataItems GetAll(DataSourceContext context, Select selectQuery) + { + if (selectQuery == null) + { + return GetAll(context); + } + else + { + string query = context.Config["listUrl"].ToString(); + foreach (var f in selectQuery.Filter.FilterRules) + { + if (f.Value != null) + { + if (!query.Contains('?')) + query += "?"; + else + query += "&"; + + query += f.Field + "=" + f.Value.AsString; + } + } + + foreach (var g in selectQuery.Filter.FilterGroups) + { + foreach (var f in g.FilterRules) + { + if (f.Value != null) + { + if (!query.Contains('?')) + query += "?"; + else + query += "&"; + + query += f.Field + "=" + f.Value.AsString; + } + } + } + + foreach (var f in selectQuery.Query.FilterRules) + { + if (f.Value != null) + { + if (!query.Contains('?')) + query += "?"; + else + query += "&"; + + query += f.Field + "=" + f.Value.AsString; + } + else if (f.MultiValue != null) + + { + foreach (var val in f.MultiValue) + { + if (!query.Contains('?')) + query += "?"; + else + query += "&"; + + query += f.Field + "=" + val.AsString; + } + + } + } + + if (!query.Contains('?')) + query += "?"; + else + query += "&"; + + query += "PageIndex=" + selectQuery.PageIndex; + + if (!query.Contains('?')) + query += "?"; + else + query += "&"; + + query += "PageSize=" + selectQuery.PageSize; + + JArray items = new JArray(); + + //var url = context.Config["listUrl"].ToString()+query; + + var url = query; + + using (var client = new HttpClient()) + { + var response = client.GetAsync(url).GetAwaiter().GetResult(); ; + response.EnsureSuccessStatusCode(); + var responseBody = response.Content.ReadAsStringAsync(); + var content = responseBody.GetAwaiter().GetResult(); + items = JArray.Parse(content); + } + var dataList = items + .Select(content => CreateDefaultDataItem(content)); + + return new DefaultDataItems() + { + Items = dataList, + Total = dataList.Count() + }; + + + //var ruleCategory = selectQuery.Filter.FilterRules.FirstOrDefault(f => f.Field == "Category"); + //if (ruleCategory != null) + //{ + // string category = ruleCategory.Value.AsString; + + //} + } + } + + public override IDataItem Get(DataSourceContext context, string id) + { + JObject item = new JObject(); + var url = context.Config["detailUrl"].ToString(); + + using (var client = new HttpClient()) + { + var response = client.GetAsync(string.Format(url, id)).GetAwaiter().GetResult(); ; + response.EnsureSuccessStatusCode(); + var responseBody = response.Content.ReadAsStringAsync(); + var content = responseBody.GetAwaiter().GetResult(); + item = JObject.Parse(content); + } + + if (item == null) + { + App.Services.Logger.Warn($"Item not shown because no content item found. Id [{id}]. url : [{url}], Id: [{id}]"); + LogContext.Log(context.ActiveModuleId, "Get DataItem", "Result", "not item found with id " + id); + } + else + { + var dataItem = CreateDefaultDataItem(item); + if (LogContext.IsLogActive) + { + LogContext.Log(context.ActiveModuleId, "Get DataItem", "Result", dataItem.Data); + } + return dataItem; + } + return null; + } + + /// + /// Gets additional/related data of a datasource. + /// + /// The context. + /// The Scope. (portal, tab, module, tabmodule) + /// The unique key in the scope + /// + public override IDataItem GetData(DataSourceContext context, string scope, string key) + { + if (context.Config[key + "Url"] == null) + { + return base.GetData(context, scope, key); + } + + JToken item = new JArray(); + var url = context.Config[key + "Url"].ToString(); + + using (var client = new HttpClient()) + { + var response = client.GetAsync(string.Format(url, key)).GetAwaiter().GetResult(); ; + response.EnsureSuccessStatusCode(); + var responseBody = response.Content.ReadAsStringAsync(); + var content = responseBody.GetAwaiter().GetResult(); + item = JToken.Parse(content); + } + if (item != null) + { + var dataItem = new DefaultDataItem() + { + Data = item, + CreatedByUserId = 1, + Item = null + }; + if (LogContext.IsLogActive) + { + LogContext.Log(context.ActiveModuleId, "Get Data", key, dataItem.Data); + } + return dataItem; + } + return null; + } + private static DefaultDataItem CreateDefaultDataItem(JToken content) + { + return new DefaultDataItem + { + Id = content["id"].ToString(), + Key = content["id"].ToString(), + Collection = "Items", + Title = content["title"]?.ToString(), + Data = content, + CreatedByUserId = 1, + LastModifiedByUserId = 1, + LastModifiedOnDate = DateTime.Now, + CreatedOnDate = DateTime.Now, + Item = null + }; + } + + } + + + +} \ No newline at end of file diff --git a/OpenContent/Components/DnnEntitiesAPIController.cs b/OpenContent/Components/DnnEntitiesAPIController.cs index b469e76b..7c3be3d7 100644 --- a/OpenContent/Components/DnnEntitiesAPIController.cs +++ b/OpenContent/Components/DnnEntitiesAPIController.cs @@ -152,7 +152,7 @@ public HttpResponseMessage ImagesLookupExt(string q, string folder, string itemK { var module = OpenContentModuleConfig.Create(ActiveModule, PortalSettings); var folderManager = FolderManager.Instance; - string imageFolder = "OpenContent/Files/" + ActiveModule.ModuleID; + string imageFolder = "OpenContent/Files/" + module.DataModule.ModuleId; if (module.Settings.Manifest.DeleteFiles) { if (!string.IsNullOrEmpty(itemKey)) @@ -451,6 +451,7 @@ public HttpResponseMessage Files(string q, string d) [HttpPost] public HttpResponseMessage CropImage(CropResizeDTO cropData) { + var module = OpenContentModuleConfig.Create(ActiveModule, PortalSettings); FilesStatus fs = null; try { @@ -473,7 +474,7 @@ public HttpResponseMessage CropImage(CropResizeDTO cropData) } rawImageUrl = rawImageUrl.Replace(PortalSettings.HomeDirectory, ""); var file = fileManager.GetFile(ActiveModule.PortalID, rawImageUrl); - string cropfolder = "OpenContent/Cropped/" + ActiveModule.ModuleID; + string cropfolder = "OpenContent/Cropped/" + module.DataModule.ModuleId; if (!string.IsNullOrEmpty(cropData.itemKey)) { cropfolder += "/" + cropData.itemKey; @@ -560,6 +561,7 @@ public HttpResponseMessage CropImage(CropResizeDTO cropData) [HttpPost] public HttpResponseMessage CropImages(CroppersDTO cropData) { + var module = OpenContentModuleConfig.Create(ActiveModule, PortalSettings); try { var res = new CroppersResultDTO(); @@ -574,7 +576,7 @@ public HttpResponseMessage CropImages(CroppersDTO cropData) var file = fileManager.GetFile(ActiveModule.PortalID, rawImageUrl); if (file != null) { - string cropfolder = "OpenContent/Files/" + ActiveModule.ModuleID; + string cropfolder = "OpenContent/Files/" + module.DataModule.ModuleId; if (!string.IsNullOrEmpty(cropData.cropfolder)) { cropfolder = cropData.cropfolder; @@ -675,6 +677,7 @@ private CropResizeResultDTO CropFile(IFileInfo file, string newFilename, CropDTO [HttpPost] public HttpResponseMessage DownloadFile(FileDTO req) { + var module = OpenContentModuleConfig.Create(ActiveModule, PortalSettings); try { var folderManager = FolderManager.Instance; @@ -686,7 +689,7 @@ public HttpResponseMessage DownloadFile(FileDTO req) } RawImageUrl = RawImageUrl.Replace(PortalSettings.HomeDirectory, ""); var file = fileManager.GetFile(ActiveModule.PortalID, RawImageUrl); - string uploadfolder = "OpenContent/Files/" + ActiveModule.ModuleID; + string uploadfolder = "OpenContent/Files/" + module.DataModule.ModuleId; if (!string.IsNullOrEmpty(req.uploadfolder)) { uploadfolder = req.uploadfolder; diff --git a/OpenContent/Components/FeatureController.cs b/OpenContent/Components/FeatureController.cs index 192d1e82..46b4a5a6 100644 --- a/OpenContent/Components/FeatureController.cs +++ b/OpenContent/Components/FeatureController.cs @@ -28,11 +28,14 @@ using DotNetNuke.Common.Internal; using DotNetNuke.Services.Search.Controllers; using Satrabel.OpenContent.Components.Datasource; +using Satrabel.OpenContent.Components.Datasource.Search; using Satrabel.OpenContent.Components.Dnn; using Satrabel.OpenContent.Components.Handlebars; using Satrabel.OpenContent.Components.Json; using Satrabel.OpenContent.Components.Lucene; +using Satrabel.OpenContent.Components.Lucene.Config; using Satrabel.OpenContent.Components.TemplateHelpers; +using PortalInfo = DotNetNuke.Entities.Portals.PortalInfo; namespace Satrabel.OpenContent.Components { @@ -47,7 +50,7 @@ public string ExportModule(int moduleId) var tabModules = ModuleController.Instance.GetTabModulesByModule(moduleId); Hashtable moduleSettings = tabModules.Any() ? tabModules.First().ModuleSettings : new Hashtable(); - + var items = ctrl.GetContents(moduleId); xml += ""; foreach (var item in items) @@ -66,7 +69,7 @@ public string ExportModule(int moduleId) xml += "" + XmlUtils.XMLEncode(moduleSetting.Value.ToString()) + ""; xml += ""; } - + xml += ""; return xml; } @@ -113,7 +116,7 @@ public void ImportModule(int moduleId, string content, string version, int userI } } module = OpenContentModuleConfig.Create(moduleId, Null.NullInteger, PortalSettings.Current); - + LuceneUtils.ReIndexModuleData(module); } @@ -163,6 +166,9 @@ public override IList GetModifiedSearchDocuments(ModuleInfo modI { App.Services.Logger.Trace($"Indexing content {modInfo.ModuleID}|{modInfo.CultureCode} - NOT - No content found"); } + + var ps = new PortalSettings(modInfo.PortalID); + foreach (IDataItem content in contentList.Items) { if (content == null) @@ -173,29 +179,38 @@ public override IList GetModifiedSearchDocuments(ModuleInfo modI && content.LastModifiedOnDate.ToUniversalTime() < DateTime.UtcNow) { SearchDocument searchDoc; + + var portalLocales = DnnLanguageUtils.GetPortalLocales(modInfo.PortalID); + if (DnnLanguageUtils.IsMultiLingualPortal(modInfo.PortalID)) { - // first process the default language module - var culture = modInfo.CultureCode; - var localizedData = GetLocalizedContent(content.Data, culture); - searchDoc = CreateSearchDocument(modInfo, module.Settings, localizedData, content.Id, culture, content.Title, content.LastModifiedOnDate.ToUniversalTime()); - searchDocuments.Add(searchDoc); - App.Services.Logger.Trace($"Indexing content {modInfo.ModuleID}|{culture} - OK! {searchDoc.Title} ({modInfo.TabID}) of {content.LastModifiedOnDate.ToUniversalTime()}"); - - // now do the same with any linked localized instances of this module - if (modInfo.LocalizedModules != null) - foreach (var localizedModule in modInfo.LocalizedModules) - { - culture = localizedModule.Value.CultureCode; - localizedData = GetLocalizedContent(content.Data, culture); - searchDoc = CreateSearchDocument(modInfo, module.Settings, localizedData, content.Id, culture, content.Title, content.LastModifiedOnDate.ToUniversalTime()); - searchDocuments.Add(searchDoc); - App.Services.Logger.Trace($"Indexing content {modInfo.ModuleID}|{culture} - OK! {searchDoc.Title} ({modInfo.TabID}) of {content.LastModifiedOnDate.ToUniversalTime()}"); - } + if (string.IsNullOrEmpty(modInfo.CultureCode)) + { + // it's a neutral language module according to DNN, which means we will need to add the neutral language content too + var culture = ps.DefaultLanguage; + var localizedData = GetLocalizedContent(content.Data, culture); + // pass "" as culture to indicate we're indexing the neutral language here + searchDoc = CreateSearchDocument(ps, modInfo, module.Settings, localizedData, content.Id, "", content.Title, content.LastModifiedOnDate.ToUniversalTime()); + searchDocuments.Add(searchDoc); + App.Services.Logger.Trace($"Indexing content {modInfo.ModuleID}|{culture} - OK! {searchDoc.Title} ({modInfo.TabID}) of {content.LastModifiedOnDate.ToUniversalTime()}"); + } + // now start creating the docs for specific cultures + foreach (var portalLocale in portalLocales.Keys) + { + var localizedData = GetLocalizedContent(content.Data, portalLocale); + searchDoc = CreateSearchDocument(ps, modInfo, module.Settings, localizedData, content.Id, portalLocale, content.Title, content.LastModifiedOnDate.ToUniversalTime()); + searchDocuments.Add(searchDoc); + App.Services.Logger.Trace($"Indexing content {modInfo.ModuleID}|{portalLocale} - OK! {searchDoc.Title} ({modInfo.TabID}) of {content.LastModifiedOnDate.ToUniversalTime()}"); + } } else { - searchDoc = CreateSearchDocument(modInfo, module.Settings, content.Data, content.Id, "", content.Title, content.LastModifiedOnDate.ToUniversalTime()); + // to make ML-Templates be correctly indexed by DNN, we need to use GetLocalizedContent with the default culture + // for sites with only one culture + var culture = portalLocales.First().Key ?? ""; + var localizedData = string.IsNullOrEmpty(culture) ? content.Data : GetLocalizedContent(content.Data, culture); + // we are intentionally still passing "" as culture to tell DNN it's the neutral language content + searchDoc = CreateSearchDocument(ps, modInfo, module.Settings, localizedData, content.Id, "", content.Title, content.LastModifiedOnDate.ToUniversalTime()); searchDocuments.Add(searchDoc); App.Services.Logger.Trace($"Indexing content {modInfo.ModuleID}|{modInfo.CultureCode} - OK! {searchDoc.Title} ({modInfo.TabID}) of {content.LastModifiedOnDate.ToUniversalTime()}"); } @@ -217,11 +232,10 @@ private static JToken GetLocalizedContent(JToken contentData, string culture) return retval; } - private static SearchDocument CreateSearchDocument(ModuleInfo modInfo, OpenContentSettings settings, JToken contentData, string itemId, string culture, string dataItemTitle, DateTime time) + private static SearchDocument CreateSearchDocument(PortalSettings ps, ModuleInfo modInfo, OpenContentSettings settings, JToken contentData, string itemId, string culture, string dataItemTitle, DateTime time) { // existance of settings.Template.Main has already been checked: we wouldn't be here if it doesn't exist // but still, we don't want to count on that too much - var ps = new PortalSettings(modInfo.PortalID); ps.PortalAlias = PortalAliasController.Instance.GetPortalAlias(ps.DefaultPortalAlias); string url = null; @@ -235,6 +249,14 @@ private static SearchDocument CreateSearchDocument(ModuleInfo modInfo, OpenConte // With a signle template we don't want to identify the content by id. url = TestableGlobals.Instance.NavigateURL(modInfo.TabID, ps, ""); } + // chek if we have a dnnSearchUrl field + // if we have, we use the OpenContent url as default + if (!string.IsNullOrEmpty(settings.Template?.Main?.DnnSearchUrl)) + { + var dicForHbs = JsonUtils.JsonToDictionary(contentData.ToString()); + var hbEngine = new HandlebarsEngine(); + url = hbEngine.ExecuteWithoutFaillure(settings.Template.Main.DnnSearchUrl, dicForHbs, url); + } // instanciate the search document var retval = new SearchDocument @@ -411,6 +433,44 @@ public string UpgradeModule(string version) { LuceneUtils.IndexAll(); } + else if (version == "04.07.00") + { + // Given the changed behavior with time int publishedEndDate, we need to Update the lucene index for all items. + foreach (PortalInfo portal in PortalController.Instance.GetPortals()) + { + var portalId = portal.PortalID; + IEnumerable modules = (new ModuleController()).GetModules(portalId).Cast(); + modules = modules.Where(m => m.ModuleDefinition.DefinitionName == App.Config.Opencontent && m.IsDeleted == false && !m.OpenContentSettings().IsOtherModule); + foreach (var module in modules) + { + try + { + var ocConfig = OpenContentModuleConfig.Create(module, new PortalSettings(portalId)); + var dsContext = OpenContentUtils.CreateDataContext(ocConfig); + var indexConfig = OpenContentUtils.GetIndexConfig(new FolderUri(dsContext.TemplateFolder), dsContext.Collection); + if (dsContext.Index) + { + if (indexConfig.HasField(App.Config.FieldNamePublishEndDate)) + { + IDataSource ds = DataSourceManager.GetDataSource(ocConfig.Settings.Manifest.DataSource); + foreach (var dataItem in ds.GetAll(dsContext, new Select()).Items) + { + var content = (OpenContentInfo)dataItem.Item; + content.HydrateDefaultFields(indexConfig, ocConfig.Settings?.Manifest?.UsePublishTime ?? false); + LuceneController.Instance.Update(content, indexConfig); + } + LuceneController.Instance.Commit(); + } + } + } + catch (Exception e) + { + App.Services.Logger.Error("Error during upgrade to 4.7.0: reindex all modules to fix.", e); + } + } + } + + } return version + res; } diff --git a/OpenContent/Components/FileUploadController.cs b/OpenContent/Components/FileUploadController.cs index 6741bbd1..67beb64c 100644 --- a/OpenContent/Components/FileUploadController.cs +++ b/OpenContent/Components/FileUploadController.cs @@ -60,6 +60,7 @@ public HttpResponseMessage UploadFile() catch (Exception exc) { Logger.Error(exc); + throw; } return IframeSafeJson(statuses); } @@ -78,6 +79,7 @@ public HttpResponseMessage UploadEasyImage() catch (Exception exc) { Logger.Error(exc); + throw; } return new HttpResponseMessage { diff --git a/OpenContent/Components/Handlebars/HandlebarsEngine.cs b/OpenContent/Components/Handlebars/HandlebarsEngine.cs index d8b33401..c2de5ee0 100644 --- a/OpenContent/Components/Handlebars/HandlebarsEngine.cs +++ b/OpenContent/Components/Handlebars/HandlebarsEngine.cs @@ -424,9 +424,20 @@ private static void RegisterEachPublishedHelper(HandlebarsDotNet.IHandlebars hbs try { DateTime publishstartdate = DateTime.Parse(item.publishstartdate, null, System.Globalization.DateTimeStyles.RoundtripKind); - if (publishstartdate.Date >= DateTime.Today) + // check if we need to compare the time + if (publishstartdate.TimeOfDay.TotalMilliseconds > 0) { - show = false; + if (publishstartdate >= DateTime.Now) + { + show = false; + } + } + else + { + if (publishstartdate.Date >= DateTime.Today) + { + show = false; + } } } catch (Exception) @@ -435,9 +446,20 @@ private static void RegisterEachPublishedHelper(HandlebarsDotNet.IHandlebars hbs try { DateTime publishenddate = DateTime.Parse(item.publishenddate, null, System.Globalization.DateTimeStyles.RoundtripKind); - if (publishenddate.Date <= DateTime.Today) + // check if we need to compare the time + if (publishenddate.TimeOfDay.TotalMilliseconds > 0) { - show = false; + if (publishenddate <= DateTime.Now) + { + show = false; + } + } + else + { + if (publishenddate.Date <= DateTime.Today) + { + show = false; + } } } catch (Exception) @@ -584,6 +606,16 @@ private static void RegisterImageUrlHelper(HandlebarsDotNet.IHandlebars hbs) writer.WriteSafeString(imageUrl); } }); + hbs.RegisterHelper("imageediturl", (writer, context, parameters) => + { + if (parameters.Length == 1) //{{imageediturl ImageId}} + { + string imageId = parameters[0].ToString(); + ImageUri imageObject = ImageUriFactory.CreateImageUri(imageId); + var imageUrl = imageObject == null ? string.Empty : imageObject.EditUrl(); + writer.WriteSafeString(imageUrl); + } + }); } private static void RegisterEmailHelper(HandlebarsDotNet.IHandlebars hbs) @@ -1124,7 +1156,7 @@ private static void RegisterContainsHelper(IHandlebars hbs) { var arg1 = arguments[0].ToString(); var arg2 = arguments[1].ToString(); - res = arg2.Contains(arg1); + res = arg1.Contains(arg2); } if (res) diff --git a/OpenContent/Components/Lucene/Mapping/JsonObjectMapper.cs b/OpenContent/Components/Lucene/Mapping/JsonObjectMapper.cs index 1f394ba5..5aadb416 100644 --- a/OpenContent/Components/Lucene/Mapping/JsonObjectMapper.cs +++ b/OpenContent/Components/Lucene/Mapping/JsonObjectMapper.cs @@ -69,185 +69,194 @@ public void AddJsonToDocument(JToken json, Document doc, FieldConfig config) /// private static void Add(Document doc, string prefix, JToken token, FieldConfig fieldconfig) { - if (token is JObject) + try { - AddProperties(doc, prefix, token as JObject, fieldconfig); - } - else if (token is JArray) - { - var itemsConfig = fieldconfig?.Items; - if (fieldconfig != null && fieldconfig.Index && itemsConfig == null) + if (token is JObject) { - throw new Exception($"Error indexing Array field {prefix}. No 'Items' section defined in index.json. Please fix your index.json."); + AddProperties(doc, prefix, token as JObject, fieldconfig); } - AddArray(doc, prefix, token as JArray, itemsConfig); - } - else if (token is JValue) - { - JValue value = token as JValue; - bool index = false; - bool sort = false; - if (fieldconfig != null) + else if (token is JArray) { - index = fieldconfig.Index; - sort = fieldconfig.Sort; - if (fieldconfig.IndexType == "datetime" && value.Type == JTokenType.String) + var itemsConfig = fieldconfig?.Items; + if (fieldconfig != null && fieldconfig.Index && itemsConfig == null) { - DateTime d; - if (DateTime.TryParse(value.Value.ToString(), null, System.Globalization.DateTimeStyles.RoundtripKind, out d)) - { - value = new JValue(d); - } + throw new Exception($"Error indexing Array field {prefix}. No 'Items' section defined in index.json. Please fix your index.json."); } + AddArray(doc, prefix, token as JArray, itemsConfig); } - - switch (value.Type) //todo: simple date gets detected as string + else if (token is JValue) { - case JTokenType.Boolean: - if (index || sort) + JValue value = token as JValue; + bool index = false; + bool sort = false; + if (fieldconfig != null) + { + index = fieldconfig.Index; + sort = fieldconfig.Sort; + if (fieldconfig.IndexType == "datetime" && value.Type == JTokenType.String) { - doc.Add(new NumericField(prefix, Field.Store.NO, true).SetIntValue((bool)value.Value ? 1 : 0)); + DateTime d; + if (DateTime.TryParse(value.Value.ToString(), null, System.Globalization.DateTimeStyles.RoundtripKind, out d)) + { + value = new JValue(d); + } } - break; + } - case JTokenType.Date: - if (index || sort) - { - doc.Add(new NumericField(prefix, Field.Store.NO, true).SetLongValue(((DateTime)value.Value).Ticks)); + switch (value.Type) //todo: simple date gets detected as string + { + case JTokenType.Boolean: + if (index || sort) + { + doc.Add(new NumericField(prefix, Field.Store.NO, true).SetIntValue((bool)value.Value ? 1 : 0)); + } + break; + + case JTokenType.Date: + if (index || sort) + { + doc.Add(new NumericField(prefix, Field.Store.NO, true).SetLongValue(((DateTime)value.Value).Ticks)); - //doc.Add(new Field(prefix, DateTools.DateToString((DateTime)value.Value, DateTools.Resolution.SECOND), Field.Store.NO, Field.Index.NOT_ANALYZED)); + //doc.Add(new Field(prefix, DateTools.DateToString((DateTime)value.Value, DateTools.Resolution.SECOND), Field.Store.NO, Field.Index.NOT_ANALYZED)); - /* - if (field != null ){ - if (field.IndexType == "datetime") + /* + if (field != null ){ + if (field.IndexType == "datetime") + { + doc.Add(new Field(prefix, DateTools.DateToString((DateTime)value.Value, DateTools.Resolution.SECOND), Field.Store.NO, Field.Index.NOT_ANALYZED)); + } + else if (field.IndexType == "date") + { + doc.Add(new Field(prefix, DateTools.DateToString((DateTime)value.Value, DateTools.Resolution.DAY), Field.Store.NO, Field.Index.NOT_ANALYZED)); + } + else if (field.IndexType == "time") + { + doc.Add(new Field(prefix, DateTools.DateToString((DateTime)value.Value, DateTools.Resolution.SECOND).Substring(8), Field.Store.NO, Field.Index.NOT_ANALYZED)); + } + } + else { doc.Add(new Field(prefix, DateTools.DateToString((DateTime)value.Value, DateTools.Resolution.SECOND), Field.Store.NO, Field.Index.NOT_ANALYZED)); } - else if (field.IndexType == "date") + */ + } + break; + + case JTokenType.Float: + if (index || sort) + { + if (value.Value is float) { - doc.Add(new Field(prefix, DateTools.DateToString((DateTime)value.Value, DateTools.Resolution.DAY), Field.Store.NO, Field.Index.NOT_ANALYZED)); + doc.Add(new NumericField(prefix, Field.Store.NO, true).SetFloatValue((float)value.Value)); } - else if (field.IndexType == "time") + else { - doc.Add(new Field(prefix, DateTools.DateToString((DateTime)value.Value, DateTools.Resolution.SECOND).Substring(8), Field.Store.NO, Field.Index.NOT_ANALYZED)); + doc.Add(new NumericField(prefix, Field.Store.NO, true).SetFloatValue((float)Convert.ToDouble(value.Value))); } } - else - { - doc.Add(new Field(prefix, DateTools.DateToString((DateTime)value.Value, DateTools.Resolution.SECOND), Field.Store.NO, Field.Index.NOT_ANALYZED)); - } - */ - } - break; + break; - case JTokenType.Float: - if (index || sort) - { - if (value.Value is float) + case JTokenType.Guid: + if (index || sort) { - doc.Add(new NumericField(prefix, Field.Store.NO, true).SetFloatValue((float)value.Value)); + doc.Add(new Field(prefix, value.Value.ToString(), Field.Store.NO, Field.Index.NOT_ANALYZED)); } - else + break; + + case JTokenType.Integer: + if (index || sort) { - doc.Add(new NumericField(prefix, Field.Store.NO, true).SetFloatValue((float)Convert.ToDouble(value.Value))); + doc.Add(new NumericField(prefix, Field.Store.NO, true).SetFloatValue((float)Convert.ToInt64(value.Value))); } - } - break; + break; - case JTokenType.Guid: - if (index || sort) - { - doc.Add(new Field(prefix, value.Value.ToString(), Field.Store.NO, Field.Index.NOT_ANALYZED)); - } - break; - - case JTokenType.Integer: - if (index || sort) - { - doc.Add(new NumericField(prefix, Field.Store.NO, true).SetFloatValue((float)Convert.ToInt64(value.Value))); - } - break; - - case JTokenType.Null: - break; + case JTokenType.Null: + break; - case JTokenType.String: + case JTokenType.String: + if (value.Value == null) + break; - if (fieldconfig != null && fieldconfig.IndexType == "key") - { - doc.Add(new Field(prefix, QueryParser.Escape(value.Value.ToString()), Field.Store.NO, Field.Index.NOT_ANALYZED)); - } - else if (fieldconfig != null && fieldconfig.IndexType == "html") - { - if (index) + if (fieldconfig != null && fieldconfig.IndexType == "key") { - doc.Add(new Field(prefix, CleanHtml(value.Value.ToString(), true), Field.Store.NO, Field.Index.ANALYZED)); + doc.Add(new Field(prefix, QueryParser.Escape(value.Value.ToString()), Field.Store.NO, Field.Index.NOT_ANALYZED)); } - if (sort) + else if (fieldconfig != null && fieldconfig.IndexType == "html") { - doc.Add(new Field("@" + prefix, CleanHtml(Truncate(value.Value.ToString(), 100), true), Field.Store.NO, Field.Index.NOT_ANALYZED)); + if (index) + { + doc.Add(new Field(prefix, CleanHtml(value.Value.ToString(), true), Field.Store.NO, Field.Index.ANALYZED)); + } + if (sort) + { + doc.Add(new Field("@" + prefix, CleanHtml(Truncate(value.Value.ToString(), 100), true), Field.Store.NO, Field.Index.NOT_ANALYZED)); + } } - } - else if (fieldconfig != null && fieldconfig.IndexType == "file") - { - var val = value.Value.ToString(); - if (!string.IsNullOrEmpty(val)) + else if (fieldconfig != null && fieldconfig.IndexType == "file") { - var fileIndexer = FileIndexerManager.GetFileIndexer(val); - if (fileIndexer != null) + var val = value.Value.ToString(); + if (!string.IsNullOrEmpty(val)) { - var content = fileIndexer.GetContent(val); - if (index) + var fileIndexer = FileIndexerManager.GetFileIndexer(val); + if (fileIndexer != null) { - doc.Add(new Field(prefix, content, Field.Store.NO, Field.Index.ANALYZED)); - } - if (sort) - { - doc.Add(new Field("@" + prefix, Truncate(content, 100), Field.Store.NO, Field.Index.NOT_ANALYZED)); + var content = fileIndexer.GetContent(val); + if (index) + { + doc.Add(new Field(prefix, content, Field.Store.NO, Field.Index.ANALYZED)); + } + if (sort) + { + doc.Add(new Field("@" + prefix, Truncate(content, 100), Field.Store.NO, Field.Index.NOT_ANALYZED)); + } } } } - } - else - { - - var val = SelectQueryDefinition.RemoveDiacritics(value.Value.ToString()); - val = val.Replace('-', ' '); // concider '-' as a space - val = val.Replace(',', ' '); // concider ',' as a space - //var val = LuceneUtils.CleanupText(value.Value.ToString()); - if (index) + else { - doc.Add(new Field(prefix, val, Field.Store.NO, Field.Index.ANALYZED)); + var val = SelectQueryDefinition.RemoveDiacritics(value.Value.ToString()); + val = val.Replace('-', ' '); // concider '-' as a space + val = val.Replace(',', ' '); // concider ',' as a space + //var val = LuceneUtils.CleanupText(value.Value.ToString()); + if (index) + { + doc.Add(new Field(prefix, val, Field.Store.NO, Field.Index.ANALYZED)); + } + if (sort) + { + doc.Add(new Field("@" + prefix, Truncate(val, 100), Field.Store.NO, Field.Index.NOT_ANALYZED)); + } } - if (sort) + break; + + case JTokenType.TimeSpan: + if (index || sort) { - doc.Add(new Field("@" + prefix, Truncate(val, 100), Field.Store.NO, Field.Index.NOT_ANALYZED)); + doc.Add(new NumericField(prefix, Field.Store.NO, true).SetLongValue(((TimeSpan)value.Value).Ticks)); } - } - break; + break; - case JTokenType.TimeSpan: - if (index || sort) - { - doc.Add(new NumericField(prefix, Field.Store.NO, true).SetLongValue(((TimeSpan)value.Value).Ticks)); - } - break; - - case JTokenType.Uri: - if (index || sort) - { - doc.Add(new Field(prefix, value.Value.ToString(), Field.Store.NO, Field.Index.ANALYZED)); - } - break; + case JTokenType.Uri: + if (index || sort) + { + doc.Add(new Field(prefix, value.Value.ToString(), Field.Store.NO, Field.Index.ANALYZED)); + } + break; - default: - Debug.Fail("Unsupported JValue type: " + value.Type); - break; + default: + Debug.Fail("Unsupported JValue type: " + value.Type); + break; + } + } + else + { + Debug.Fail("Unsupported JToken: " + token); } } - else + catch (Exception e) { - Debug.Fail("Unsupported JToken: " + token); + if(Debugger.IsAttached) Debugger.Break(); + throw; } } diff --git a/OpenContent/Components/Manifest/Manifest.cs b/OpenContent/Components/Manifest/Manifest.cs index a8d90792..98cb5729 100644 --- a/OpenContent/Components/Manifest/Manifest.cs +++ b/OpenContent/Components/Manifest/Manifest.cs @@ -75,7 +75,9 @@ public Dictionary AdditionalDataDefinition [JsonProperty(PropertyName = "deleteFiles")] public bool DeleteFiles { get; set; } - + [JsonProperty(PropertyName = "usePublishTime")] + public bool UsePublishTime { get; set; } + public bool HasTemplates => (Templates != null); public FolderUri ManifestDir { get; set; } diff --git a/OpenContent/Components/Manifest/TemplateFiles.cs b/OpenContent/Components/Manifest/TemplateFiles.cs index ab3e346a..519001c3 100644 --- a/OpenContent/Components/Manifest/TemplateFiles.cs +++ b/OpenContent/Components/Manifest/TemplateFiles.cs @@ -63,6 +63,15 @@ public TemplateFiles() [JsonProperty(PropertyName = "dnnSearchText")] public string DnnSearchText { get; set; } + /// + /// Gets or sets a value specifying the url to use for the indexed document in [DNN search]. + /// + /// + /// You can use a Handlebars template. + /// + [JsonProperty(PropertyName = "dnnSearchUrl")] + public string DnnSearchUrl { get; set; } + [JsonProperty(PropertyName = "model")] public Dictionary Model { get; set; } } diff --git a/OpenContent/Components/OpenContentAPIController.cs b/OpenContent/Components/OpenContentAPIController.cs index dc9b862a..4dfdf4c4 100644 --- a/OpenContent/Components/OpenContentAPIController.cs +++ b/OpenContent/Components/OpenContentAPIController.cs @@ -96,10 +96,10 @@ public HttpResponseMessage Edit(string id) } json["options"]["form"]["buttons"] = newButtons; } - string itemKey; + string itemKey; if (dsItem == null) { - itemKey= ObjectId.NewObjectId().ToString(); + itemKey = ObjectId.NewObjectId().ToString(); } else { diff --git a/OpenContent/Components/OpenContentInfo.cs b/OpenContent/Components/OpenContentInfo.cs index d539702b..e5b6feba 100644 --- a/OpenContent/Components/OpenContentInfo.cs +++ b/OpenContent/Components/OpenContentInfo.cs @@ -37,8 +37,13 @@ public OpenContentInfo(string json) } public int ContentId { get; set; } + + /// + /// OpenContent item Key, unique across all modules. Typically populated with ObjectId.NewObjectId().ToString() + /// [ColumnName("DocumentKey")] public string Key { get; internal set; } + [IgnoreColumn] public string Id { @@ -73,7 +78,14 @@ public JToken JsonAsJToken { if (_jsonAsJToken == null && !string.IsNullOrEmpty(this.Json)) { - _jsonAsJToken = JToken.Parse(this.Json); + try + { + _jsonAsJToken = JToken.Parse(this.Json); + } + catch (Exception e) + { + throw new Exception($"Failed to parse json from moduleId:{ModuleId}, contentId:{ContentId}", e); + } } // JsonAsJToken is modified (to remove other cultures) return _jsonAsJToken?.DeepClone(); diff --git a/OpenContent/Components/Querying/QueryBuilder.cs b/OpenContent/Components/Querying/QueryBuilder.cs index e5ab9c7d..a67d25a9 100644 --- a/OpenContent/Components/Querying/QueryBuilder.cs +++ b/OpenContent/Components/Querying/QueryBuilder.cs @@ -271,23 +271,23 @@ private void AddWorkflowFilter(FilterGroup filter) if (_indexConfig?.Fields != null && _indexConfig.Fields.ContainsKey(App.Config.FieldNamePublishStartDate)) { //DateTime startDate = DateTime.MinValue; - //DateTime endDate = DateTime.Today; + //DateTime endDate = DateTime.Now; filter.AddRule(new FilterRule() { Field = App.Config.FieldNamePublishStartDate, - Value = new DateTimeRuleValue(DateTime.Today), + Value = new DateTimeRuleValue(DateTime.Now), FieldOperator = OperatorEnum.LESS_THEN_OR_EQUALS, FieldType = FieldTypeEnum.DATETIME }); } if (_indexConfig?.Fields != null && _indexConfig.Fields.ContainsKey(App.Config.FieldNamePublishEndDate)) { - //DateTime startDate = DateTime.Today; + //DateTime startDate = DateTime.Now; //DateTime endDate = DateTime.MaxValue; filter.AddRule(new FilterRule() { Field = App.Config.FieldNamePublishEndDate, - Value = new DateTimeRuleValue(DateTime.Today), + Value = new DateTimeRuleValue(DateTime.Now), FieldOperator = OperatorEnum.GREATER_THEN_OR_EQUALS, FieldType = FieldTypeEnum.DATETIME }); diff --git a/OpenContent/Components/Render/ModelFactoryBase.cs b/OpenContent/Components/Render/ModelFactoryBase.cs index ca46ebf9..78033339 100644 --- a/OpenContent/Components/Render/ModelFactoryBase.cs +++ b/OpenContent/Components/Render/ModelFactoryBase.cs @@ -296,8 +296,7 @@ protected void ExtendModel(JObject model, bool onlyData, bool onlyMainData, stri context["ModuleId"] = _module.ViewModule.ModuleId; context["GoogleApiKey"] = App.Services.CreateGlobalSettingsRepository(_portalId).GetGoogleApiKey(); context["ModuleTitle"] = _module.ViewModule.ModuleTitle; - var editIsAllowed = !_manifest.DisableEdit && !_templateManifest.DisableEdit && IsEditAllowed(-1); - context["IsEditable"] = editIsAllowed; //allowed to edit the item or list (meaning allow Add) + context["IsEditMode"] = IsEditMode; context["PortalId"] = _portalId; context["MainUrl"] = _module.GetUrl(_detailTabId, GetCurrentCultureCode()); @@ -305,8 +304,13 @@ protected void ExtendModel(JObject model, bool onlyData, bool onlyMainData, stri context["HTTPAlias"] = _module.HostName; context["PortalName"] = _module.PortalName; context["TemplatePath"] = _module.Settings.TemplateDir.UrlFolder; - context["TemplateName"] = (String.IsNullOrEmpty(_manifest.Title) ? Path.GetFileName(_templateManifest.MainTemplateUri().FolderPath) : _manifest.Title) +" - "+ (string.IsNullOrEmpty(_templateManifest.Title) ? _templateManifest.Key.ShortKey : _templateManifest.Title); - //context["TemplateName"] = _templateManifest.MainTemplateUri().UrlFilePath ; + if (_templateManifest != null) + { + var editIsAllowed = !_manifest.DisableEdit && !_templateManifest.DisableEdit && IsEditAllowed(-1); + context["IsEditable"] = editIsAllowed; //allowed to edit the item or list (meaning allow Add) + context["TemplateName"] = (String.IsNullOrEmpty(_manifest.Title) ? Path.GetFileName(_templateManifest.MainTemplateUri().FolderPath) : _manifest.Title) + " - " + (string.IsNullOrEmpty(_templateManifest.Title) ? _templateManifest.Key.ShortKey : _templateManifest.Title); + //context["TemplateName"] = _templateManifest.MainTemplateUri().UrlFilePath ; + } } } @@ -540,7 +544,8 @@ private JToken GenerateObject(string id, int tabId, int moduleId, bool onlyData) var context = new JObject(); json["Context"] = context; context["Id"] = dataItem.Id; - context["DetailUrl"] = GenerateDetailUrl(dataItem, json, module.Settings.Manifest, GetCurrentCultureCode(), tabId > 0 ? tabId : _detailTabId); + //context["DetailUrl"] = GenerateDetailUrl(dataItem, json, module.Settings.Manifest, GetCurrentCultureCode(), tabId > 0 ? tabId : _detailTabId); + context["DetailUrl"] = GenerateDetailUrl(dataItem, json, module.Settings.Manifest, GetCurrentCultureCode(), _detailTabId); } return json; } @@ -562,7 +567,7 @@ protected string GenerateDetailUrl(IDataItem item, JObject dyn, Manifest.Manifes url = hbEngine.Execute(manifest.DetailUrl, dynForHBS); url = HttpUtility.HtmlDecode(url); } - return _module.GetUrl(_detailTabId, cultureCode, url.CleanupUrl(), "id=" + item.Id); + return _module.GetUrl(detailTabId, cultureCode, url.CleanupUrl(), "id=" + item.Id); } } } \ No newline at end of file diff --git a/OpenContent/Components/Render/ModelFactoryMultiple.cs b/OpenContent/Components/Render/ModelFactoryMultiple.cs index c4410bd7..0a3e132d 100644 --- a/OpenContent/Components/Render/ModelFactoryMultiple.cs +++ b/OpenContent/Components/Render/ModelFactoryMultiple.cs @@ -103,8 +103,11 @@ public override JToken GetModelAsJson(bool onlyData = false, bool onlyMainData = } else { - var editStatus = !_manifest.DisableEdit && !_templateManifest.DisableEdit && IsEditAllowed(item.CreatedByUserId); - context["IsEditable"] = editStatus; + if (_templateManifest != null) + { + var editStatus = !_manifest.DisableEdit && !_templateManifest.DisableEdit && IsEditAllowed(item.CreatedByUserId); + context["IsEditable"] = editStatus; + } if (HasEditPermissions(item.CreatedByUserId)) { context["EditUrl"] = _module.EditUrl("id", item.Id, _module.ViewModule.ModuleId); diff --git a/OpenContent/Components/Render/RenderEngine.cs b/OpenContent/Components/Render/RenderEngine.cs index f65c305d..78e265e6 100644 --- a/OpenContent/Components/Render/RenderEngine.cs +++ b/OpenContent/Components/Render/RenderEngine.cs @@ -366,7 +366,7 @@ private void GetDetailData(RenderInfo info, OpenContentModuleConfig module) { var indexConfig = OpenContentUtils.GetIndexConfig(info.Template); string raison; - if (!OpenContentUtils.HaveViewPermissions(dsItem, module.UserRoles.FromDnnRoles(), indexConfig, out raison)) + if (!OpenContentUtils.IsViewAllowed(dsItem, module.UserRoles.FromDnnRoles(), indexConfig, out raison)) { App.Services.Logger.Error($"Error accessing {HttpContext.Current?.Request?.Url?.AbsoluteUri}. Referrer {HttpContext.Current?.Request?.UrlReferrer?.AbsoluteUri}"); if (module.ViewModule.HasEditRightsOnModule()) diff --git a/OpenContent/Components/Rest/RestController.cs b/OpenContent/Components/Rest/RestController.cs index b107a38b..910eb29c 100644 --- a/OpenContent/Components/Rest/RestController.cs +++ b/OpenContent/Components/Rest/RestController.cs @@ -54,7 +54,7 @@ public HttpResponseMessage Get(string entity, string id) var mf = new ModelFactorySingle(dsItem, module, collection); string raison = ""; - if (!OpenContentUtils.HaveViewPermissions(dsItem, module.UserRoles.FromDnnRoles(), indexConfig, out raison)) + if (!OpenContentUtils.IsViewAllowed(dsItem, module.UserRoles.FromDnnRoles(), indexConfig, out raison)) { Exceptions.ProcessHttpException(new HttpException(404, "No detail view permissions for id=" + id + " (" + raison + ")")); //throw new UnauthorizedAccessException("No detail view permissions for id " + info.DetailItemId); diff --git a/OpenContent/Components/Rest/V2/RestController.cs b/OpenContent/Components/Rest/V2/RestController.cs index 15849a8d..d290f09d 100644 --- a/OpenContent/Components/Rest/V2/RestController.cs +++ b/OpenContent/Components/Rest/V2/RestController.cs @@ -55,7 +55,7 @@ public HttpResponseMessage Get(string entity, string id) var mf = new ModelFactorySingle(dsItem, module, collection); string raison = ""; - if (!OpenContentUtils.HaveViewPermissions(dsItem, module.UserRoles.FromDnnRoles(), indexConfig, out raison)) + if (!OpenContentUtils.IsViewAllowed(dsItem, module.UserRoles.FromDnnRoles(), indexConfig, out raison)) { Exceptions.ProcessHttpException(new HttpException(404, "No detail view permissions for id=" + id + " (" + raison + ")")); //throw new UnauthorizedAccessException("No detail view permissions for id " + info.DetailItemId); diff --git a/OpenContent/Components/Utils/OpenContentUtils.cs b/OpenContent/Components/Utils/OpenContentUtils.cs index a8be462b..e7ce1595 100644 --- a/OpenContent/Components/Utils/OpenContentUtils.cs +++ b/OpenContent/Components/Utils/OpenContentUtils.cs @@ -25,7 +25,7 @@ namespace Satrabel.OpenContent.Components { public static class OpenContentUtils { - public static void HydrateDefaultFields(this OpenContentInfo content, FieldConfig indexConfig) + public static void HydrateDefaultFields(this OpenContentInfo content, FieldConfig indexConfig, bool usePublishTime = false) { if (indexConfig.HasField(App.Config.FieldNamePublishStartDate) && content.JsonAsJToken != null && content.JsonAsJToken[App.Config.FieldNamePublishStartDate] == null) @@ -33,9 +33,26 @@ public static void HydrateDefaultFields(this OpenContentInfo content, FieldConfi content.JsonAsJToken[App.Config.FieldNamePublishStartDate] = DateTime.MinValue; } if (indexConfig.HasField(App.Config.FieldNamePublishEndDate) - && content.JsonAsJToken != null && content.JsonAsJToken[App.Config.FieldNamePublishEndDate] == null) + && content.JsonAsJToken != null) { - content.JsonAsJToken[App.Config.FieldNamePublishEndDate] = DateTime.MaxValue; + if (content.JsonAsJToken[App.Config.FieldNamePublishEndDate] == null) + { + content.JsonAsJToken[App.Config.FieldNamePublishEndDate] = DateTime.MaxValue; + } + else + { + // if the enddata has no time, + // and we don't need to usePublishTime + // we add 23:59:59.999 as a time + var t = content.JsonAsJToken[App.Config.FieldNamePublishEndDate].Value(); + if (!usePublishTime && t.TimeOfDay.TotalMilliseconds == 0) + { + var contentJToken = content.JsonAsJToken; + contentJToken[App.Config.FieldNamePublishEndDate] = t.AddDays(1).AddMilliseconds(-1); + content.JsonAsJToken = contentJToken; + } + } + } if (indexConfig.HasField(App.Config.FieldNamePublishStatus) && content.JsonAsJToken != null && content.JsonAsJToken[App.Config.FieldNamePublishStatus] == null) @@ -63,7 +80,7 @@ public static string GetSkinTemplateFolder(PortalSettings portalSettings, string public static string GetHostTemplateFolder(PortalSettings portalSettings, string moduleSubDir) { var hostPath = "~/Portals/_Default/"; - + return hostPath + moduleSubDir + "/Templates/"; } @@ -220,7 +237,7 @@ public static List ListOfTemplatesFiles(PortalSettings portalSettings, //if (!string.IsNullOrEmpty(portalSettings.ActiveTab.SkinPath)) { basePath = HostingEnvironment.MapPath(GetSkinTemplateFolder(portalSettings, moduleSubDir)); - + dirs = GetDirs(selectedTemplate, otherModuleTemplate, advanced, basePath, dirs, lst, "Skin"); } // Host @@ -266,22 +283,25 @@ private static string[] GetDirs(TemplateManifest selectedTemplate, FileUri other if (manifest != null && manifest.HasTemplates) { manifestTemplateFound = true; - foreach (var template in manifest.Templates) + if (advanced || !manifest.Advanced) { - FileUri templateUri = new FileUri(manifestFileUri.FolderPath, template.Key); - string templateName = Path.GetDirectoryName(manifestFile).Substring(basePath.Length).Replace("\\", " / "); - if (!String.IsNullOrEmpty(template.Value.Title)) + foreach (var template in manifest.Templates) { - if (advanced) - templateName = templateName + " - " + template.Value.Title; + FileUri templateUri = new FileUri(manifestFileUri.FolderPath, template.Key); + string templateName = Path.GetDirectoryName(manifestFile).Substring(basePath.Length).Replace("\\", " / "); + if (!String.IsNullOrEmpty(template.Value.Title)) + { + if (advanced) + templateName = templateName + " - " + template.Value.Title; + } + var item = new ListItem((templateCat == "Site" ? "" : templateCat + " : ") + templateName, templateUri.FilePath); + if (selectedTemplate != null && templateUri.FilePath.ToLowerInvariant() == selectedTemplate.Key.ToString().ToLowerInvariant()) + { + item.Selected = true; + } + lst.Add(item); + if (!advanced) break; } - var item = new ListItem((templateCat == "Site" ? "" : templateCat + " : ") + templateName, templateUri.FilePath); - if (selectedTemplate != null && templateUri.FilePath.ToLowerInvariant() == selectedTemplate.Key.ToString().ToLowerInvariant()) - { - item.Selected = true; - } - lst.Add(item); - if (!advanced) break; } } } @@ -622,81 +642,140 @@ internal static bool FormExist(FolderUri folder) return false; } - internal static bool HaveViewPermissions(IDataItem dsItem, IList userRoles, FieldConfig indexConfig, out string raison) + internal static bool IsViewAllowed(IDataItem dsItem, IList userRoles, FieldConfig indexConfig, out string raison) { raison = ""; if (dsItem?.Data == null) return true; - bool permissions = true; //publish status , dates - if (indexConfig?.Fields != null && indexConfig.Fields.ContainsKey(App.Config.FieldNamePublishStatus)) - { - permissions = dsItem.Data[App.Config.FieldNamePublishStatus] != null && - dsItem.Data[App.Config.FieldNamePublishStatus].ToString() == "published"; - if (!permissions) raison = App.Config.FieldNamePublishStatus + $" being {dsItem.Data[App.Config.FieldNamePublishStatus]}"; - } - if (permissions && indexConfig?.Fields != null && indexConfig.Fields.ContainsKey(App.Config.FieldNamePublishStartDate)) + var isAllowed = IsPublished(dsItem, indexConfig, out raison); + // user and roles + if (isAllowed) isAllowed = HaveViewPermissions(dsItem, userRoles, indexConfig, out raison); + + return isAllowed; + } + /// + /// Check the user's permissions to view the item. NOTE: as of 2021-05-22 use IsViewAllowed to also check publish status and date. + /// + /// + /// + /// + /// + /// + internal static bool HaveViewPermissions(IDataItem dsItem, IList userRoles, FieldConfig indexConfig, out string reason) + { + reason = ""; + if (dsItem?.Data == null) return true; + + bool permissions = true; + + // Roles + string fieldName = ""; + if (indexConfig?.Fields != null && indexConfig.Fields.ContainsKey("userrole")) { - permissions = dsItem.Data[App.Config.FieldNamePublishStartDate] != null && - dsItem.Data[App.Config.FieldNamePublishStartDate].Type == JTokenType.Date && - ((DateTime)dsItem.Data[App.Config.FieldNamePublishStartDate]) <= DateTime.Today; - if (!permissions) raison = App.Config.FieldNamePublishStartDate + $" being {dsItem.Data[App.Config.FieldNamePublishStartDate]}"; + fieldName = "userrole"; } - if (permissions && indexConfig?.Fields != null && indexConfig.Fields.ContainsKey(App.Config.FieldNamePublishEndDate)) + else if (indexConfig?.Fields != null && indexConfig.Fields.ContainsKey("userroles")) { - permissions = dsItem.Data[App.Config.FieldNamePublishEndDate] != null && - dsItem.Data[App.Config.FieldNamePublishEndDate].Type == JTokenType.Date && - ((DateTime)dsItem.Data[App.Config.FieldNamePublishEndDate]) >= DateTime.Today; - if (!permissions) raison = App.Config.FieldNamePublishEndDate + $" being {dsItem.Data[App.Config.FieldNamePublishEndDate]}"; + fieldName = "userroles"; } - if (permissions) + if (!String.IsNullOrEmpty(fieldName)) { - // Roles - string fieldName = ""; - if (indexConfig?.Fields != null && indexConfig.Fields.ContainsKey("userrole")) + permissions = false; + string[] dataRoles = { }; + if (dsItem.Data[fieldName] != null) { - fieldName = "userrole"; + if (dsItem.Data[fieldName].Type == JTokenType.Array) + { + dataRoles = ((JArray)dsItem.Data[fieldName]).Select(d => d.ToString()).ToArray(); + } + else + { + dataRoles = new string[] { dsItem.Data[fieldName].ToString() }; + } } - else if (indexConfig?.Fields != null && indexConfig.Fields.ContainsKey("userroles")) + if (dataRoles.Contains("AllUsers")) { - fieldName = "userroles"; + permissions = true; } - if (!String.IsNullOrEmpty(fieldName)) + else { - permissions = false; - string[] dataRoles = { }; - if (dsItem.Data[fieldName] != null) + var roles = userRoles; + if (roles.Any()) { - if (dsItem.Data[fieldName].Type == JTokenType.Array) - { - dataRoles = ((JArray)dsItem.Data[fieldName]).Select(d => d.ToString()).ToArray(); - } - else - { - dataRoles = new string[] { dsItem.Data[fieldName].ToString() }; - } + permissions = roles.Any(r => dataRoles.Contains(r.RoleId.ToString())); } - if (dataRoles.Contains("AllUsers")) + else { - permissions = true; + permissions = dataRoles.Contains("Unauthenticated"); + } + } + if (!permissions) reason = fieldName; + } + return permissions; + + } + + internal static bool IsPublished(IDataItem dsItem, FieldConfig indexConfig, out string reason) + { + reason = ""; + if (dsItem?.Data == null) return true; + + bool isPublished = true; + if (indexConfig?.Fields != null && indexConfig.Fields.ContainsKey(App.Config.FieldNamePublishStatus)) + { + isPublished = dsItem.Data[App.Config.FieldNamePublishStatus] != null && + dsItem.Data[App.Config.FieldNamePublishStatus].ToString() == "published"; + if (!isPublished) reason = App.Config.FieldNamePublishStatus + $" being {dsItem.Data[App.Config.FieldNamePublishStatus]}"; + } + if (isPublished && indexConfig?.Fields != null && indexConfig.Fields.ContainsKey(App.Config.FieldNamePublishStartDate)) + { + if (dsItem.Data[App.Config.FieldNamePublishStartDate] != null && dsItem.Data[App.Config.FieldNamePublishStartDate].Type == JTokenType.Date) + { + var compareDate = (DateTime)dsItem.Data[App.Config.FieldNamePublishStartDate]; + // do we need to compare time? + if (compareDate.TimeOfDay.TotalMilliseconds > 0) + { + isPublished = compareDate <= DateTime.Now; } else { - var roles = userRoles; - if (roles.Any()) - { - permissions = roles.Any(r => dataRoles.Contains(r.RoleId.ToString())); - } - else - { - permissions = dataRoles.Contains("Unauthenticated"); - } + isPublished = compareDate <= DateTime.Today; } - if (!permissions) raison = fieldName; } + else + { + // not a date + isPublished = false; + } + + if (!isPublished) reason = App.Config.FieldNamePublishStartDate + $" being {dsItem.Data[App.Config.FieldNamePublishStartDate]}"; + } + if (isPublished && indexConfig?.Fields != null && indexConfig.Fields.ContainsKey(App.Config.FieldNamePublishEndDate)) + { + if (dsItem.Data[App.Config.FieldNamePublishEndDate] != null && dsItem.Data[App.Config.FieldNamePublishEndDate].Type == JTokenType.Date) + { + var compareDate = (DateTime)dsItem.Data[App.Config.FieldNamePublishEndDate]; + // do we need to compare time? + if (compareDate.TimeOfDay.TotalMilliseconds > 0) + { + isPublished = compareDate >= DateTime.Now; + } + else + { + isPublished = compareDate >= DateTime.Today; + } + } + else + { + // not a date + isPublished = false; + } + + if (!isPublished) reason = App.Config.FieldNamePublishEndDate + $" being {dsItem.Data[App.Config.FieldNamePublishEndDate]}"; } - return permissions; + return isPublished; } } } \ No newline at end of file diff --git a/OpenContent/OpenContent.csproj b/OpenContent/OpenContent.csproj index 856002cc..804d5b0e 100644 --- a/OpenContent/OpenContent.csproj +++ b/OpenContent/OpenContent.csproj @@ -175,6 +175,7 @@ AddEdit.ascx + diff --git a/OpenContent/OpenContent.dnn b/OpenContent/OpenContent.dnn index f5b1a8f6..b9d3b42b 100644 --- a/OpenContent/OpenContent.dnn +++ b/OpenContent/OpenContent.dnn @@ -1,6 +1,6 @@ - + OpenContent OpenContent module by Satrabel.be ~/DesktopModules/OpenContent/Images/icon_extensions.png @@ -272,7 +272,7 @@ Satrabel.OpenContent.Components.FeatureController [DESKTOPMODULEID] - 02.01.00 + 02.01.00,04.07.00 @@ -292,6 +292,10 @@ Handlebars.dll bin + + + EPPlus.dll + bin diff --git a/OpenContent/Properties/AssemblyInfo.cs b/OpenContent/Properties/AssemblyInfo.cs index 17c00363..506ad783 100644 --- a/OpenContent/Properties/AssemblyInfo.cs +++ b/OpenContent/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -9,7 +9,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Satrabel")] [assembly: AssemblyProduct("OpenContent")] -[assembly: AssemblyCopyright("Copyright © 2015-2020")] +[assembly: AssemblyCopyright("Copyright ? 2015-2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -30,5 +30,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("04.05.00.0")] -[assembly: AssemblyFileVersion("04.05.00.0")] +[assembly: AssemblyVersion("04.07.00.00")] +[assembly: AssemblyFileVersion("04.07.00.00")] diff --git a/appveyor.yml b/appveyor.yml index 65bc14ce..9bee6970 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 04.05.00.{build}-{branch} +version: 04.07.00.{build}-{branch} assembly_info: patch: true file: '**\AssemblyInfo.*'