From b78aeaaa75e0ed3e448115121a4f63e75688a2f5 Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 7 Dec 2018 13:26:43 +0100 Subject: [PATCH 01/44] Data objects implementing ICreationHistory - CreationTime and CreatedBy are only set at insert time if undefined, fix #640 --- Composite/Data/DataFacadeImpl.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Composite/Data/DataFacadeImpl.cs b/Composite/Data/DataFacadeImpl.cs index 571819ad79..6747898b58 100644 --- a/Composite/Data/DataFacadeImpl.cs +++ b/Composite/Data/DataFacadeImpl.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -720,11 +720,14 @@ private static void SetCreationHistoryInformation(object sender, DataEventArgs d ICreationHistory data = dataEventArgs.Data as ICreationHistory; if (data != null) { - data.CreationDate = DateTime.Now; + if (data.CreationDate == DateTime.MinValue) + { + data.CreationDate = DateTime.Now; + } try { - if (UserValidationFacade.IsLoggedIn()) + if (string.IsNullOrEmpty(data.CreatedBy) && UserValidationFacade.IsLoggedIn()) { data.CreatedBy = UserValidationFacade.GetUsername(); } From 1c27f5a75d3695eb216a2532a53bb0ab26ff963b Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 3 Jan 2019 14:15:57 +0100 Subject: [PATCH 02/44] Fix #644 (ordering alphabetically would fail when adding to an empty list, which was posibble programatically). --- Composite/Data/Types/PageInsertPosition.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Composite/Data/Types/PageInsertPosition.cs b/Composite/Data/Types/PageInsertPosition.cs index 4f908c1d74..ff83f8b291 100644 --- a/Composite/Data/Types/PageInsertPosition.cs +++ b/Composite/Data/Types/PageInsertPosition.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Composite.C1Console.Users; @@ -89,7 +89,7 @@ orderby ps.LocalOrdering var cultureInfo = UserSettings.CultureInfo; - int targetLocalOrdering = -1; + int targetLocalOrdering = pageStructures.Any() ? -1 : 0; foreach (IPageStructure pageStructure in pageStructures) { From 75858267bf04b07e2f55db59b7dfaca3ff903991 Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 25 Jan 2019 14:51:49 +0100 Subject: [PATCH 03/44] Fix #648 - fixing issue where meta tags having a property attribute was intermediately ordered so a final "grab deepest id dubplicate" would grab the exact opposite of what it should. --- Composite/Core/WebClient/Renderings/Page/PageRenderer.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs index 621490dcdb..a9f73003a3 100644 --- a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs +++ b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs @@ -642,11 +642,8 @@ public static void NormalizeXhtmlDocument(XhtmlDocument rootDocument) rootDocument.Root.Add(nestedDocument.Attributes().Except(rootDocument.Root.Attributes(), _nameBasedAttributeComparer)); - // making from nested documents appear first. We will not filter them later and this ensure desired precedence - bool IsMetaProperty(XElement e) => e.Name.LocalName == "meta" && e.Attribute("property") != null; + rootDocument.Head.Add(nestedHead.Nodes()); - rootDocument.Head.AddFirst(nestedHead.Elements().Where(IsMetaProperty)); - rootDocument.Head.Add(nestedHead.Nodes().Where(f => !(f is XElement e && IsMetaProperty(e)))); rootDocument.Head.Add(nestedHead.Attributes().Except(rootDocument.Head.Attributes(), _nameBasedAttributeComparer)); rootDocument.Body.Add(nestedBody.Attributes().Except(rootDocument.Body.Attributes(), _nameBasedAttributeComparer)); From c74d084e80e6de55fa035f0b8aa004fe20430e3e Mon Sep 17 00:00:00 2001 From: Christoph Keller Date: Tue, 19 Feb 2019 13:05:30 +0100 Subject: [PATCH 04/44] #652: Added consoleId as Query, including backward-compatibility check if the consoleId already exists in the url. --- Composite/C1Console/Actions/UrlActionToken.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Composite/C1Console/Actions/UrlActionToken.cs b/Composite/C1Console/Actions/UrlActionToken.cs index 51a2dbbfdf..4c0788d25a 100644 --- a/Composite/C1Console/Actions/UrlActionToken.cs +++ b/Composite/C1Console/Actions/UrlActionToken.cs @@ -91,6 +91,11 @@ public FlowToken Execute(EntityToken entityToken, ActionToken actionToken, FlowC string extendedUrl = $"{url}{(url.Contains("?") ? "&" : "?")}EntityToken={HttpUtility.UrlEncode(serializedEntityToken)}"; + if (extendedUrl.IndexOf("consoleId", StringComparison.OrdinalIgnoreCase) == -1) + { + extendedUrl = $"{extendedUrl}&consoleId={currentConsoleId}"; + } + ConsoleMessageQueueFacade.Enqueue( new OpenViewMessageQueueItem { From b81dc36e7dff846c1c0fb27540c076c5a02e8166 Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 20 Feb 2019 13:10:35 +0100 Subject: [PATCH 05/44] Updated iedriver (nightwatch testing related), previous version had vulnerability warning - see https://nvd.nist.gov/vuln/detail/CVE-2016-10562 --- Website/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Website/package.json b/Website/package.json index 9018cceffd..95bb8dc981 100644 --- a/Website/package.json +++ b/Website/package.json @@ -27,7 +27,7 @@ "grunt-contrib-watch": "~0.6.1", "grunt-postcss": "^0.5.5", "grunt-svgmin": "3.2.0", - "iedriver": "2.53.1", + "iedriver": "3.14.1", "istanbul": "^0.4.3", "istanbul-lib-coverage": "1.0.0", "jsdom": "9.4.1", From 222c055dc35ec8cfd5c251ff59d14b818038e0eb Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 1 Mar 2019 16:08:16 +0100 Subject: [PATCH 06/44] Fixing issue where DataSourceId.Serialize() fail on LocaleScope=null. --- Composite/Data/DataSourceId.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Composite/Data/DataSourceId.cs b/Composite/Data/DataSourceId.cs index e341019e36..39512cbd0c 100644 --- a/Composite/Data/DataSourceId.cs +++ b/Composite/Data/DataSourceId.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using Composite.Core.Serialization; @@ -117,7 +117,7 @@ public bool ShouldSerializeProviderName() public CultureInfo LocaleScope { get; internal set; } [JsonProperty(PropertyName = "localeScope")] - private string LocalScopeName => LocaleScope.Name; + private string LocalScopeName => (LocaleScope !=null ? LocaleScope.Name : null); /// /// True when the data element represents a physically stored element From d9676d03e078296b369e872f9521487c39f21d00 Mon Sep 17 00:00:00 2001 From: Christoph Keller Date: Fri, 1 Mar 2019 16:56:39 +0100 Subject: [PATCH 07/44] #654: Removed Scrollbox Id from HtmlBlob (not used anywhere). --- .../FormsControls/FormUiControlTemplates/Text/HtmlBlob.ascx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Website/Composite/controls/FormsControls/FormUiControlTemplates/Text/HtmlBlob.ascx b/Website/Composite/controls/FormsControls/FormUiControlTemplates/Text/HtmlBlob.ascx index c7f5800ede..6019071249 100644 --- a/Website/Composite/controls/FormsControls/FormUiControlTemplates/Text/HtmlBlob.ascx +++ b/Website/Composite/controls/FormsControls/FormUiControlTemplates/Text/HtmlBlob.ascx @@ -16,7 +16,7 @@ } - + <%= ConvertRenderingUrls(this.Html) %> From e0d7b72e5287bc127f369c78a02e02d4baf1cb63 Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 7 Mar 2019 17:26:45 +0100 Subject: [PATCH 08/44] Making empty folders and missing folders behave the same (not throwing exception, always empty list). --- .../IO/IOProviders/LocalIOProvider/LocalC1Directory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Composite/Plugins/IO/IOProviders/LocalIOProvider/LocalC1Directory.cs b/Composite/Plugins/IO/IOProviders/LocalIOProvider/LocalC1Directory.cs index 72809d0b62..5794007665 100644 --- a/Composite/Plugins/IO/IOProviders/LocalIOProvider/LocalC1Directory.cs +++ b/Composite/Plugins/IO/IOProviders/LocalIOProvider/LocalC1Directory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Composite.Core.IO; using Composite.Core.IO.Plugins.IOProvider; @@ -127,7 +127,7 @@ public string[] GetFiles(string path, string searchPattern) [System.Diagnostics.CodeAnalysis.SuppressMessage("Composite.IO", "Composite.DoNotUseDirectoryClass:DoNotUseDirectoryClass")] public string[] GetFiles(string path, string searchPattern, SearchOption searchOption) { - return Directory.GetFiles(path, searchPattern, searchOption); + return Directory.Exists(path) ? Directory.GetFiles(path, searchPattern, searchOption) : new string[] { }; } From 9b7ef7c68dbf5e01d003c3df4085ddce04c176ff Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 13 Mar 2019 13:22:04 +0100 Subject: [PATCH 09/44] Passing null as an exception to LoggerService should not throw an exception --- Composite/Core/Logging/LoggingService.cs | 29 ++++++++++++------------ 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/Composite/Core/Logging/LoggingService.cs b/Composite/Core/Logging/LoggingService.cs index e5650d271e..40e20244da 100644 --- a/Composite/Core/Logging/LoggingService.cs +++ b/Composite/Core/Logging/LoggingService.cs @@ -63,23 +63,23 @@ static private void LogEntry(string title, string message, Exception exc, Catego static private void LogEntry(string title, string message, Exception exc, Category category, System.Diagnostics.TraceEventType severity, int priority, int eventid) { - var entry = new Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry(); - - // TODO: refactor this code - entry.Title = string.Format("({0} - {1}) {2}", AppDomain.CurrentDomain.Id, Thread.CurrentThread.ManagedThreadId, title); - entry.Message = message; - entry.Severity = severity; - entry.Priority = priority; - entry.EventId = eventid; + var entry = new Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry + { + Title = $"({AppDomain.CurrentDomain.Id} - {Thread.CurrentThread.ManagedThreadId}) {title}", + Message = message, + Severity = severity, + Priority = priority, + EventId = eventid + }; if ((category & Category.General) != 0) { - entry.Categories.Add("General"); + entry.Categories.Add(nameof(Category.General)); } if ((category & Category.Audit) != 0) { - entry.Categories.Add("Audit"); + entry.Categories.Add(nameof(Category.Audit)); } if (exc != null) @@ -259,11 +259,10 @@ private static void OnFlush(FlushEventArgs args) static string PrettyExceptionCallStack(Exception ex) { - string serializedException = ex.ToString(); + if (ex == null) return "[exception is null]"; - string cleanException; - - if (ex.InnerException != null && ExcludeInnerExceptionInformation(ex, serializedException, out cleanException)) + string serializedException = ex.ToString(); + if (ex.InnerException != null && ExcludeInnerExceptionInformation(ex, serializedException, out var cleanException)) { return PrettyExceptionCallStack(ex.InnerException) + Environment.NewLine + cleanException; } @@ -323,7 +322,7 @@ public static void Initialize(Resources resources) } else { - string path = Path.Combine(PathUtil.BaseDirectory, string.Format("logging{0}.config", Guid.NewGuid())); + string path = Path.Combine(PathUtil.BaseDirectory, $"logging{Guid.NewGuid()}.config"); using (C1StreamWriter writer = new C1StreamWriter(path)) { From 494190a2629a7ac42efd7a09fcbae28fb6d14f12 Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 14 Mar 2019 17:06:51 +0100 Subject: [PATCH 10/44] For reading "taioted" composite.config configuration sections (config file changed since last load) adding a "retry once" strategy. Ensures that multiple package install ations, all changing Composite.config, can run in the same process. Fix #657 --- .../FileConfigurationSourceImplementation.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Composite/Core/Configuration/FileConfigurationSourceImplementation.cs b/Composite/Core/Configuration/FileConfigurationSourceImplementation.cs index e5db0d6a8f..107358c21b 100644 --- a/Composite/Core/Configuration/FileConfigurationSourceImplementation.cs +++ b/Composite/Core/Configuration/FileConfigurationSourceImplementation.cs @@ -1,4 +1,4 @@ -//=============================================================================== +//=============================================================================== // Microsoft patterns & practices Enterprise Library // Core //=============================================================================== @@ -9,6 +9,7 @@ // FITNESS FOR A PARTICULAR PURPOSE. //=============================================================================== +using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; using System.Collections.Generic; @@ -47,8 +48,19 @@ public FileConfigurationSourceImplementation(string configurationFilepath, bool public override System.Configuration.ConfigurationSection GetSection(string sectionName) { System.Configuration.Configuration configuration = GetConfiguration(); + System.Configuration.ConfigurationSection configurationSection; - System.Configuration.ConfigurationSection configurationSection = configuration.GetSection(sectionName) as System.Configuration.ConfigurationSection; + try + { + configurationSection = configuration.GetSection(sectionName) as System.Configuration.ConfigurationSection; + } + catch (System.Configuration.ConfigurationException ex) + { + // retry once + UpdateCache(); + configuration = GetConfiguration(); + configurationSection = configuration.GetSection(sectionName) as System.Configuration.ConfigurationSection; + } SetConfigurationWatchers(sectionName, configurationSection); From dbb189e2afd9437be23cfb5be58ade6f35864dc2 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 18 Mar 2019 16:57:53 +0100 Subject: [PATCH 11/44] Fixing FriendlyUrl cache not being updated after an external change --- .../Plugins/Routing/Pages/DefaultPageUrlProvider.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Composite/Plugins/Routing/Pages/DefaultPageUrlProvider.cs b/Composite/Plugins/Routing/Pages/DefaultPageUrlProvider.cs index 654ef4dfc4..915284973b 100644 --- a/Composite/Plugins/Routing/Pages/DefaultPageUrlProvider.cs +++ b/Composite/Plugins/Routing/Pages/DefaultPageUrlProvider.cs @@ -39,6 +39,13 @@ static DefaultPageUrlProvider() { DataEvents.OnAfterAdd += (a, b) => UpdateFriendlyUrl((IPage) b.Data); DataEvents.OnAfterUpdate += (a, b) => UpdateFriendlyUrl((IPage) b.Data); + DataEvents.OnStoreChanged += (sender, args) => + { + if (!args.DataEventsFired) + { + lock (_friendlyUrls) _friendlyUrls.Clear(); + } + }; DataEvents.OnAfterAdd += (a, b) => _hostnameBindings = null; DataEvents.OnAfterUpdate += (a, b) => _hostnameBindings = null; @@ -425,7 +432,7 @@ private static void UpdateFriendlyUrl(IPage page) private PageUrlData ParsePagePath(string pagePath, PublicationScope publicationScope, CultureInfo locale, IHostnameBinding hostnameBinding) { - // Parshing what's left: + // Parsing what's left: // [/Path to a page][UrlSuffix]{/PathInfo} string pathInfo = null; From 0efd3845e1a657a06cb84cd7e041b9bfb6a78570 Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 4 Apr 2019 17:14:02 +0200 Subject: [PATCH 12/44] Logging reason when xml data files cannot be read and added at least one retry - in order to better guard against / identify 3rd party processes intefering with file access. --- .../XmlDataProviderDocumentCache.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderDocumentCache.cs b/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderDocumentCache.cs index 9873e2b6a5..d0a99ea631 100644 --- a/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderDocumentCache.cs +++ b/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderDocumentCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -269,24 +269,28 @@ private static FileRecord LoadFileRecordFromDisk(string filePath, string element IList candidateFiles = GetCandidateFiles(filePath); + string errorCause = ""; + foreach (C1FileInfo candidateFile in candidateFiles) { bool tryLoad = true; + bool retriedLoad = false; while (tryLoad && dataDocument == null) { - dataDocument = TryLoad(candidateFile); + dataDocument = TryLoad(candidateFile, ref errorCause); if (dataDocument == null) { - if ((DateTime.Now - candidateFile.LastWriteTime).TotalSeconds > 30) + if (retriedLoad && (DateTime.Now - candidateFile.LastWriteTime).TotalSeconds > 30) { - tryLoad = false; + tryLoad = false; } else { Thread.Sleep(250); // other processes/servers may be writing to this file at the moment. Patience young padawan! } + retriedLoad = true; } } @@ -300,7 +304,7 @@ private static FileRecord LoadFileRecordFromDisk(string filePath, string element if (dataDocument == null) { dataDocument = new XDocument(new XElement("fallback")); - Log.LogWarning(LogTitle, "Did not find a healthy XML document for '{0}' - creating an empty store.", filePath); + Log.LogWarning(LogTitle, "Did not find a healthy XML document for '{0}' - creating an empty store. {1}", filePath, errorCause); } List elements = ExtractElements(dataDocument); @@ -350,7 +354,7 @@ private static FileRecord LoadFileRecordFromDisk(string filePath, string element - private static XDocument TryLoad(C1FileInfo candidateFile) + private static XDocument TryLoad(C1FileInfo candidateFile, ref string errorCause) { XDocument dataDocument = null; try @@ -362,8 +366,9 @@ private static XDocument TryLoad(C1FileInfo candidateFile) dataDocument = XDocument.Load(xmlReader); } } - catch (Exception) + catch (Exception ex) { + errorCause = ex.Message; // broken file - should not stop us... } From 8e4d4669c2e7aab6561b8d4bc5974ff84e2129b5 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Wed, 8 May 2019 09:26:29 +0300 Subject: [PATCH 13/44] Fix #666 [Bug] Date Field has disappeared after select date in Chrome Version 74 --- .../scripts/source/top/ui/UserInterfaceMapping.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Website/Composite/scripts/source/top/ui/UserInterfaceMapping.js b/Website/Composite/scripts/source/top/ui/UserInterfaceMapping.js index 09d48f1640..0a104c9112 100644 --- a/Website/Composite/scripts/source/top/ui/UserInterfaceMapping.js +++ b/Website/Composite/scripts/source/top/ui/UserInterfaceMapping.js @@ -12,17 +12,12 @@ function UserInterfaceMapping ( map ) { /** * @type {HashMap} */ - if (Client.isExplorer) { - this.map = {}; - for (var m in map) { - this.map[m.replace("ui:", "")] = map[m]; - } - } - else { - this.map = map; - } + this.map = {}; - + for (var m in map) { + this.map[m.replace('ui:', '')] = map[m]; + this.map[m] = map[m]; + } } /** From 3bfefb4880fefb6fd1bd9f5b2366801cf7050ca3 Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 22 May 2019 16:31:06 +0200 Subject: [PATCH 14/44] On package install, DataPackageFragmentInsaller will use default culture, if user culture is not available. Previously the package install would raise an error. Motivation: For setup flows, where user auth is changed via a package, user information cannot be expected to be available within the same request. --- .../DataPackageFragmentInstaller.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs index 250a49f9a1..afa2739080 100644 --- a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs +++ b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs @@ -120,9 +120,12 @@ public override IEnumerable Install() } else if (dataType.AddToCurrentLocale) { - Verify.That(UserValidationFacade.IsLoggedIn(), "Cannot add to the current locale as there's no logged in user."); + var currentLocale = DataLocalizationFacade.DefaultLocalizationCulture; + if (UserValidationFacade.IsLoggedIn()) + { + currentLocale = UserSettings.ActiveLocaleCultureInfo; + } - var currentLocale = UserSettings.ActiveLocaleCultureInfo; using (new DataScope(currentLocale)) { XElement element = AddData(dataType, currentLocale); From 47b9027811234352a81597566431e64fe9837741 Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 24 May 2019 13:48:13 +0200 Subject: [PATCH 15/44] Fix #669 - when running C1 in a virtual folder, pre-compile of asp.net filebased handlers would fail and fill log with warnings. --- Composite/Core/WebClient/BuildManagerHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Composite/Core/WebClient/BuildManagerHelper.cs b/Composite/Core/WebClient/BuildManagerHelper.cs index 687c6669a0..a3344aa5f6 100644 --- a/Composite/Core/WebClient/BuildManagerHelper.cs +++ b/Composite/Core/WebClient/BuildManagerHelper.cs @@ -114,7 +114,8 @@ private static void LoadAllControls() { using (new DisableUrlMedataScope()) { - BuildManager.GetCompiledType(virtualPath); + string compilePath = (virtualPath.StartsWith("~") ? "" : "~") + virtualPath; + BuildManager.GetCompiledType(compilePath); } } catch (ThreadAbortException) From ca43b1c7fbdf5df21684b367e4ebcb299678865f Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 24 May 2019 14:53:40 +0200 Subject: [PATCH 16/44] URL Configuration, alias to hostname redirect - adding "Redirect Permanently (HTTP 301)" option (before all redirects were always HTTP 302) --- .../Renderings/RequestInterceptorHttpModule.cs | 11 +++++++++-- Composite/Data/Types/IHostnameBinding.cs | 11 ++++++++++- .../content/forms/Administrative/Hostnames.xml | 8 +++++++- .../Orckestra.Tools.UrlConfiguration.en-us.xml | 5 ++++- Website/WebSite.csproj | 1 + 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Composite/Core/WebClient/Renderings/RequestInterceptorHttpModule.cs b/Composite/Core/WebClient/Renderings/RequestInterceptorHttpModule.cs index 4a2cb99ec8..22a0ee05f0 100644 --- a/Composite/Core/WebClient/Renderings/RequestInterceptorHttpModule.cs +++ b/Composite/Core/WebClient/Renderings/RequestInterceptorHttpModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Web; using Composite.Core.Extensions; @@ -220,7 +220,14 @@ static bool CheckForHostnameAliasRedirect(HttpContext httpContext) string newUrl = request.Url.AbsoluteUri.Replace("://" + hostname, "://" + hostnameBinding.Hostname); - httpContext.Response.Redirect(newUrl, false); + if (hostnameBinding.UsePermanentRedirect) + { + httpContext.Response.RedirectPermanent(newUrl, false); + } + else + { + httpContext.Response.Redirect(newUrl, false); + } httpContext.ApplicationInstance.CompleteRequest(); return true; } diff --git a/Composite/Data/Types/IHostnameBinding.cs b/Composite/Data/Types/IHostnameBinding.cs index 2b1723ee14..fdada4a497 100644 --- a/Composite/Data/Types/IHostnameBinding.cs +++ b/Composite/Data/Types/IHostnameBinding.cs @@ -1,4 +1,4 @@ -using System; +using System; using Composite.Data.Hierarchy; using Composite.Data.Hierarchy.DataAncestorProviders; using Microsoft.Practices.EnterpriseLibrary.Validation.Validators; @@ -70,5 +70,14 @@ public interface IHostnameBinding : IData [DefaultFieldBoolValue(false)] [ImmutableFieldId("{1F45F2E7-2FAF-4F33-ACCA-BFF32EF7818A}")] bool EnforceHttps { get; set; } + + /// + /// When set to true, the system will use "HTTP 301 Permanent Redirect" when + /// redirecting from an alias to the hostname. + /// + [StoreFieldType(PhysicalStoreFieldType.Boolean)] + [DefaultFieldBoolValue(true)] + [ImmutableFieldId("{d933184a-19c1-4292-8e2e-f4a1a163f087}")] + bool UsePermanentRedirect { get; set; } } } diff --git a/Website/Composite/content/forms/Administrative/Hostnames.xml b/Website/Composite/content/forms/Administrative/Hostnames.xml index e4c06b646f..a74aa10fb4 100644 --- a/Website/Composite/content/forms/Administrative/Hostnames.xml +++ b/Website/Composite/content/forms/Administrative/Hostnames.xml @@ -1,4 +1,4 @@ - + @@ -9,6 +9,7 @@ + @@ -67,6 +68,11 @@ + + + + + \ No newline at end of file diff --git a/Website/Composite/localization/Orckestra.Tools.UrlConfiguration.en-us.xml b/Website/Composite/localization/Orckestra.Tools.UrlConfiguration.en-us.xml index 39e1211642..616d809f9a 100644 --- a/Website/Composite/localization/Orckestra.Tools.UrlConfiguration.en-us.xml +++ b/Website/Composite/localization/Orckestra.Tools.UrlConfiguration.en-us.xml @@ -1,4 +1,4 @@ - + @@ -37,4 +37,7 @@ + + + \ No newline at end of file diff --git a/Website/WebSite.csproj b/Website/WebSite.csproj index 2667746362..820e32b677 100644 --- a/Website/WebSite.csproj +++ b/Website/WebSite.csproj @@ -658,6 +658,7 @@ + From 37028092f9abf8986f39a231244dded3d8ea81db Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 7 Jun 2019 11:52:30 +0200 Subject: [PATCH 17/44] Data layer and editing UI for managing "Usergroup --> Active data locale" done --- .../EditUserGroupWorkflow.cs | 46 ++++++++++++++++++- Composite/Composite.csproj | 1 + .../DataStoreExistenceVerifierImpl.cs | 1 + .../Data/Types/IUserGroupActiveLocale.cs | 41 +++++++++++++++++ ...Plugins.UserGroupElementProvider.en-us.xml | 13 ++++-- 5 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 Composite/Data/Types/IUserGroupActiveLocale.cs diff --git a/Composite.Workflows/Plugins/Elements/ElementProviders/UserGroupElementProvider/EditUserGroupWorkflow.cs b/Composite.Workflows/Plugins/Elements/ElementProviders/UserGroupElementProvider/EditUserGroupWorkflow.cs index 0eea5156fb..9d0f9c7162 100644 --- a/Composite.Workflows/Plugins/Elements/ElementProviders/UserGroupElementProvider/EditUserGroupWorkflow.cs +++ b/Composite.Workflows/Plugins/Elements/ElementProviders/UserGroupElementProvider/EditUserGroupWorkflow.cs @@ -1,12 +1,14 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Workflow.Activities; using System.Xml.Linq; using Composite.C1Console.Actions; using Composite.C1Console.Events; using Composite.C1Console.Users; +using Composite.Core.Logging; using Composite.Data; using Composite.Data.DynamicTypes; using Composite.Data.Types; @@ -22,7 +24,6 @@ using Microsoft.Practices.EnterpriseLibrary.Validation; using SR = Composite.Core.ResourceSystem.StringResourceSystemFacade; -using Composite.Core.Logging; namespace Composite.Plugins.Elements.ElementProviders.UserGroupElementProvider { @@ -66,6 +67,7 @@ private void step1CodeActivity_ShowDocument_ExecuteCode(object sender, EventArgs UpdateFormDefinitionWithActivePerspectives(userGroup, bindingsElement, placeHolderElement); UpdateFormDefinitionWithGlobalPermissions(userGroup, bindingsElement, placeHolderElement); + UpdateFormDefinitionWithActiveLocalePermissions(userGroup, bindingsElement, placeHolderElement); var clientValidationRules = new Dictionary>(); clientValidationRules.Add("Name", ClientValidationRuleFacade.GetClientValidationRules(userGroup, "Name")); @@ -139,6 +141,24 @@ private void saveCodeActivity_Save_ExecuteCode(object sender, EventArgs e) UserGroupPerspectiveFacade.SetSerializedEntityTokens(userGroup.Id, newUserGroupEntityTokens); + List selectedUserGroupActiveLocales = ActiveLocalesFormsHelper.GetSelectedLocalesTypes(this.Bindings).ToList(); + + using (var connection = new DataConnection()) + { + var existingLocales = connection.Get().Where(f=> f.UserGroupId == userGroup.Id).ToList(); + var toDelete = existingLocales.Where(f => !selectedUserGroupActiveLocales.Contains(new CultureInfo(f.CultureName))); + connection.Delete(toDelete); + + foreach (var localeToAdd in selectedUserGroupActiveLocales.Where(f => !existingLocales.Any(g => g.CultureName == f.Name))) + { + var toAdd = connection.CreateNew(); + toAdd.Id = Guid.NewGuid(); + toAdd.UserGroupId = userGroup.Id; + toAdd.CultureName = localeToAdd.Name; + connection.Add(toAdd); + } + } + SetSaveStatus(true); LoggingService.LogEntry("UserManagement", @@ -206,6 +226,28 @@ private void UpdateFormDefinitionWithGlobalPermissions(IUserGroup userGroup, XEl helper.UpdateWithNewBindings(this.Bindings, permissionTypes); } + private void UpdateFormDefinitionWithActiveLocalePermissions(IUserGroup userGroup, XElement bindingsElement, XElement placeHolderElement) + { + var helper = new ActiveLocalesFormsHelper( + SR.GetString("Composite.Plugins.UserGroupElementProvider", "EditUserGroup.EditUserGroupStep1.ActiveLocalesFieldLabel"), + SR.GetString("Composite.Plugins.UserGroupElementProvider", "EditUserGroup.EditUserGroupStep1.ActiveLocalesMultiSelectLabel"), + SR.GetString("Composite.Plugins.UserGroupElementProvider", "EditUserGroup.EditUserGroupStep1.ActiveLocalesMultiSelectHelp") + ); + + bindingsElement.Add(helper.GetBindingsMarkup()); + placeHolderElement.Add(helper.GetFormMarkup()); + + EntityToken rootEntityToken = ElementFacade.GetRootsWithNoSecurity().Select(f => f.ElementHandle.EntityToken).Single(); + + using (var connection = new DataConnection()) + { + IEnumerable activeCultures = null; + activeCultures = connection.Get().Where(f => f.UserGroupId == userGroup.Id).Select(f => new CultureInfo(f.CultureName)); + helper.UpdateWithNewBindings(this.Bindings, activeCultures); + } + + } + private void UpdateFormDefinitionWithActivePerspectives(IUserGroup userGroup, XElement bindingsElement, XElement placeHolderElement) diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 794277cddf..0a4a811f99 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -256,6 +256,7 @@ + diff --git a/Composite/Data/Foundation/DataStoreExistenceVerifierImpl.cs b/Composite/Data/Foundation/DataStoreExistenceVerifierImpl.cs index d7a81ef970..7482fed427 100644 --- a/Composite/Data/Foundation/DataStoreExistenceVerifierImpl.cs +++ b/Composite/Data/Foundation/DataStoreExistenceVerifierImpl.cs @@ -66,6 +66,7 @@ internal sealed class DataStoreExistenceVerifierImpl : IDataStoreExistenceVerifi typeof(IUserDeveloperSettings), typeof(IUserFormLogin), typeof(IUserGroup), + typeof(IUserGroupActiveLocale), typeof(IUserGroupActivePerspective), typeof(IUserGroupPermissionDefinition), typeof(IUserGroupPermissionDefinitionPermissionType), diff --git a/Composite/Data/Types/IUserGroupActiveLocale.cs b/Composite/Data/Types/IUserGroupActiveLocale.cs new file mode 100644 index 0000000000..74bc7923e6 --- /dev/null +++ b/Composite/Data/Types/IUserGroupActiveLocale.cs @@ -0,0 +1,41 @@ +using System; +using Composite.Data.Hierarchy; +using Composite.Data.Hierarchy.DataAncestorProviders; +using Composite.Data.Validation.Validators; +using Microsoft.Practices.EnterpriseLibrary.Validation.Validators; + + +namespace Composite.Data.Types +{ + /// + /// + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [AutoUpdateble] + [Caching(CachingType.Full)] + [KeyPropertyName("Id")] + [DataScope(DataScopeIdentifier.PublicName)] + [ImmutableTypeId("{f60f4da8-59df-460f-84bf-5bf20700036b}")] + [DataAncestorProvider(typeof(NoAncestorDataAncestorProvider))] + public interface IUserGroupActiveLocale : IData + { + /// + [StoreFieldType(PhysicalStoreFieldType.Guid)] + [ImmutableFieldId("{80c5cc7c-bf23-4c7d-bfe0-d168eda41d50}")] + Guid Id { get; set; } + + + /// + [StoreFieldType(PhysicalStoreFieldType.Guid)] + [ImmutableFieldId("{f28ada6a-12bd-43bf-aef8-1b693ee3d390}")] + Guid UserGroupId { get; set; } + + + /// + [NotNullValidator()] + [StringSizeValidator(2, 16)] + [StoreFieldType(PhysicalStoreFieldType.String, 16)] + [ImmutableFieldId("{ddc38768-16e8-444c-a982-80f2a55b0b75}")] + string CultureName { get; set; } + } +} diff --git a/Website/Composite/localization/Composite.Plugins.UserGroupElementProvider.en-us.xml b/Website/Composite/localization/Composite.Plugins.UserGroupElementProvider.en-us.xml index d7130e8629..aab25115cb 100644 --- a/Website/Composite/localization/Composite.Plugins.UserGroupElementProvider.en-us.xml +++ b/Website/Composite/localization/Composite.Plugins.UserGroupElementProvider.en-us.xml @@ -1,4 +1,4 @@ - + @@ -20,14 +20,17 @@ - + - + + + + - - + + From bbc148b62d4a0364612719f2a6053d9ce20191c1 Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 7 Jun 2019 17:34:31 +0200 Subject: [PATCH 18/44] Making it possible to target other active consoles with messages from wf --- .../Workflow/Activities/FormsWorkflow.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs index 0a18a35c3f..03e079504e 100644 --- a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs +++ b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs @@ -572,13 +572,24 @@ protected string GetCurrentConsoleId() return managementConsoleMessageService.CurrentConsoleId; } - /// protected IEnumerable GetConsoleIdsOpenedByCurrentUser() { - string currentConsoleId = GetCurrentConsoleId(); + return GetConsoleIdsOpenedByUser(UserSettings.Username); + } - return ConsoleFacade.GetConsoleIdsByUsername(UserSettings.Username).Union(new[] { currentConsoleId }); + /// + protected IEnumerable GetConsoleIdsOpenedByUser(string username) + { + if (UserSettings.Username == username) + { + string currentConsoleId = GetCurrentConsoleId(); + return ConsoleFacade.GetConsoleIdsByUsername(username).Union(new[] { currentConsoleId }); + } + else + { + return ConsoleFacade.GetConsoleIdsByUsername(username); + } } From a0c81b03404d286857359ab70f00d1393af7ee7b Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 7 Jun 2019 17:36:12 +0200 Subject: [PATCH 19/44] Making text direction ws robust in the face of no current active locale (defaulting to ltr) --- .../Composite/services/Localization/LocalizationService.asmx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Website/Composite/services/Localization/LocalizationService.asmx b/Website/Composite/services/Localization/LocalizationService.asmx index e6b902a689..6067a1c6e3 100644 --- a/Website/Composite/services/Localization/LocalizationService.asmx +++ b/Website/Composite/services/Localization/LocalizationService.asmx @@ -1,4 +1,4 @@ -<%@ WebService Language="C#" Class="Composite.Services.LocalizationService" %> +<%@ WebService Language="C#" Class="Composite.Services.LocalizationService" %> using System; using System.Linq; @@ -139,7 +139,7 @@ namespace Composite.Services [WebMethod] public string GetTextDirection(bool dummy) { - return UserSettings.ActiveLocaleCultureInfo.TextInfo.IsRightToLeft ? "rtl" : "ltr"; + return UserSettings.ActiveLocaleCultureInfo == null ? "ltr" : (UserSettings.ActiveLocaleCultureInfo.TextInfo.IsRightToLeft ? "rtl" : "ltr"); } [WebMethod] From b0b0d05547b62822c2c88cdea86c45ac0ba75125 Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 11 Jun 2019 17:06:22 +0200 Subject: [PATCH 20/44] null check to avoid garbage exceptions on misconfigures users --- .../GenericPublishProcessController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs b/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs index 52fb6ccdcf..ff2d64def2 100644 --- a/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs +++ b/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -354,7 +354,7 @@ public List GetActions(IData data, Type elementProviderType) return new List(); } - if (data is ILocalizedControlled && !UserSettings.ActiveLocaleCultureInfo.Equals(data.DataSourceId.LocaleScope)) + if (UserSettings.ActiveLocaleCultureInfo == null || data is ILocalizedControlled && !UserSettings.ActiveLocaleCultureInfo.Equals(data.DataSourceId.LocaleScope)) { return new List(); } From bc7a5eecf26009e65618f868bc59fa14e9c20bae Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 11 Jun 2019 17:07:18 +0200 Subject: [PATCH 21/44] Form based login for users without any data language: Will display relevant error message and log out user again. --- Website/Composite/scripts/source/top/core/KickStart.js | 7 ++++++- Website/Composite/services/Login/Login.asmx | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Website/Composite/scripts/source/top/core/KickStart.js b/Website/Composite/scripts/source/top/core/KickStart.js index 981e6725c9..cec788b807 100644 --- a/Website/Composite/scripts/source/top/core/KickStart.js +++ b/Website/Composite/scripts/source/top/core/KickStart.js @@ -371,7 +371,12 @@ var KickStart = new function () { } - if (result == "success") { + if (result == "loginHasNoLanguageAccess") { + // TODO: unhardcode + alert("The login has no Data Language permissions. Please contact an administrator and ask for a data language to be assigned to your user permissions."); + } + + if (result == "success") { isAllowed = true; } } diff --git a/Website/Composite/services/Login/Login.asmx b/Website/Composite/services/Login/Login.asmx index ac8a4576f7..6ada8b541f 100644 --- a/Website/Composite/services/Login/Login.asmx +++ b/Website/Composite/services/Login/Login.asmx @@ -23,6 +23,14 @@ namespace Composite.Services { var result = UserValidationFacade.FormValidateUser(username, password); + var activeContentCulture = UserSettings.ActiveLocaleCultureInfo; + + if (activeContentCulture==null && result == LoginResult.Success) + { + UserValidationFacade.Logout(); + return "loginHasNoLanguageAccess"; + } + switch (result) { case LoginResult.Success: From 37c1ba68bd0246e410a8d4bb8a326a7beb1e5a94 Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 11 Jun 2019 17:10:36 +0200 Subject: [PATCH 22/44] Content language permissions implemented, editable. Administrative access blocked if user is detedted to have no active content language (now possible, where language permissions may come from groups as well. --- .../UserElementProvider/EditUserWorkflow.cs | 73 +++++++------------ .../C1Console/Security/UserGroupFacade.cs | 16 +++- Composite/C1Console/Users/UserSettings.cs | 33 +++++++-- ...AdministrativeDataScopeSetterHttpModule.cs | 9 ++- 4 files changed, 75 insertions(+), 56 deletions(-) diff --git a/Composite.Workflows/Plugins/Elements/ElementProviders/UserElementProvider/EditUserWorkflow.cs b/Composite.Workflows/Plugins/Elements/ElementProviders/UserElementProvider/EditUserWorkflow.cs index a553f6003a..b5b6f6bf73 100644 --- a/Composite.Workflows/Plugins/Elements/ElementProviders/UserElementProvider/EditUserWorkflow.cs +++ b/Composite.Workflows/Plugins/Elements/ElementProviders/UserElementProvider/EditUserWorkflow.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -48,6 +48,7 @@ private static class BindingNames public const string User = "User"; public const string UserFormLogin = "UserFormLogin"; public const string NewPassword = "NewPassword"; + public const string ActiveContentLanguage = "ActiveLocaleName"; } private void CheckActiveLanguagesExists(object sender, System.Workflow.Activities.ConditionalEventArgs e) @@ -80,11 +81,9 @@ private void initializeCodeActivity_ExecuteCode(object sender, EventArgs e) this.Bindings.Add("C1ConsoleUiCultures", regionLanguageList); this.Bindings.Add("C1ConsoleUiLanguageName", c1ConsoleUiLanguage.Name); - if (UserSettings.GetActiveLocaleCultureInfos(user.Username).Any() && (user.Username != UserSettings.Username)) - { - this.Bindings.Add("ActiveLocaleName", UserSettings.GetCurrentActiveLocaleCultureInfo(user.Username).Name); - this.Bindings.Add("ActiveLocaleList", DataLocalizationFacade.ActiveLocalizationCultures.ToDictionary(f => f.Name, DataLocalizationFacade.GetCultureTitle)); - } + var currentActiveCulture = UserSettings.GetCurrentActiveLocaleCultureInfo(user.Username); + this.Bindings.Add("ActiveLocaleName", currentActiveCulture != null ? currentActiveCulture.Name : null); + this.Bindings.Add("ActiveLocaleList", DataLocalizationFacade.ActiveLocalizationCultures.ToDictionary(f => f.Name, DataLocalizationFacade.GetCultureTitle)); var clientValidationRules = new Dictionary> { @@ -157,7 +156,7 @@ private void UpdateFormDefinitionWithActiveLocales(IUser user, XElement bindings bindingsElement.Add(helper.GetBindingsMarkup()); placeHolderElement.Add(helper.GetFormMarkup()); - helper.UpdateWithNewBindings(this.Bindings, UserSettings.GetActiveLocaleCultureInfos(user.Username)); + helper.UpdateWithNewBindings(this.Bindings, UserSettings.GetActiveLocaleCultureInfos(user.Username, false)); } @@ -198,41 +197,11 @@ private void saveCodeActivity_ExecuteCode(object sender, EventArgs e) List newActiveLocales = ActiveLocalesFormsHelper.GetSelectedLocalesTypes(this.Bindings).ToList(); - List currentActiveLocales = null; - CultureInfo selectedActiveLocal = null; - - if (newActiveLocales.Count > 0) - { - currentActiveLocales = UserSettings.GetActiveLocaleCultureInfos(user.Username).ToList(); + List currentActiveLocales = UserSettings.GetActiveLocaleCultureInfos(user.Username, false).ToList(); + string selectedActiveLocaleName = this.GetBinding("ActiveLocaleName"); - string selectedActiveLocaleName = (user.Username != UserSettings.Username ? - this.GetBinding("ActiveLocaleName") : - UserSettings.ActiveLocaleCultureInfo.ToString()); - - if (selectedActiveLocaleName != null) - { - selectedActiveLocal = CultureInfo.CreateSpecificCulture(selectedActiveLocaleName); - if (!newActiveLocales.Contains(selectedActiveLocal)) - { - if (user.Username != UserSettings.Username) - { - this.ShowFieldMessage("ActiveLocaleName", GetText("Website.Forms.Administrative.EditUserStep1.ActiveLocaleNotChecked")); - } - else - { - this.ShowFieldMessage("ActiveLocalesFormsHelper_Selected", GetText("Website.Forms.Administrative.EditUserStep1.NoActiveLocaleSelected")); - } - userValidated = false; - } - } - } - else - { - this.ShowFieldMessage("ActiveLocalesFormsHelper_Selected", GetText("Website.Forms.Administrative.EditUserStep1.NoActiveLocaleSelected")); - userValidated = false; - } - + CultureInfo selectedActiveLocale = CultureInfo.CreateSpecificCulture(selectedActiveLocaleName); string systemPerspectiveEntityToken = EntityTokenSerializer.Serialize(AttachingPoint.SystemPerspective.EntityToken); @@ -359,14 +328,14 @@ private void saveCodeActivity_ExecuteCode(object sender, EventArgs e) } } - if (selectedActiveLocal != null) + if (selectedActiveLocale != null) { - if (!UserSettings.GetCurrentActiveLocaleCultureInfo(user.Username).Equals(selectedActiveLocal)) + if (!selectedActiveLocale.Equals(UserSettings.GetCurrentActiveLocaleCultureInfo(user.Username))) { reloadUsersConsoles = true; } - UserSettings.SetCurrentActiveLocaleCultureInfo(user.Username, selectedActiveLocal); + UserSettings.SetCurrentActiveLocaleCultureInfo(user.Username, selectedActiveLocale); } else if (UserSettings.GetActiveLocaleCultureInfos(user.Username).Any()) { @@ -398,18 +367,26 @@ from r in oldRelations } LoggingService.LogEntry("UserManagement", - $"C1 Console user '{user.Username}' updated by '{UserValidationFacade.GetUsername()}'.", + $"C1 Console user '{user.Username}' updated by '{UserValidationFacade.GetUsername()}'.", LoggingService.Category.Audit, TraceEventType.Information); transactionScope.Complete(); } - if (reloadUsersConsoles) + if (UserSettings.GetCurrentActiveLocaleCultureInfo(user.Username) == null) + { + this.ShowFieldMessage(BindingNames.ActiveContentLanguage, "The user doesn't have permissions to access the language you selected here. Assign permissions so the user may access this."); + this.ShowMessage(DialogType.Warning, "User missing permissions for language", "The user doesn't have permissions to access the language you selected as 'Active content language'."); + } + else { - foreach (string consoleId in GetConsoleIdsOpenedByCurrentUser()) + if (reloadUsersConsoles) { - ConsoleMessageQueueFacade.Enqueue(new RebootConsoleMessageQueueItem(), consoleId); + foreach (string consoleId in GetConsoleIdsOpenedByUser(user.Username)) + { + ConsoleMessageQueueFacade.Enqueue(new RebootConsoleMessageQueueItem(), consoleId); + } } } @@ -438,7 +415,7 @@ private void IsUserLoggedOn(object sender, System.Workflow.Activities.Conditiona { CultureInfo selectedActiveLocale = CultureInfo.CreateSpecificCulture(selectedActiveLocaleName); - if (!UserSettings.GetCurrentActiveLocaleCultureInfo(user.Username).Equals(selectedActiveLocale)) + if (!selectedActiveLocale.Equals(UserSettings.GetCurrentActiveLocaleCultureInfo(user.Username))) { e.Result = ConsoleFacade.GetConsoleIdsByUsername(user.Username).Any(); return; diff --git a/Composite/C1Console/Security/UserGroupFacade.cs b/Composite/C1Console/Security/UserGroupFacade.cs index 1ca4718c57..c5678c9eda 100644 --- a/Composite/C1Console/Security/UserGroupFacade.cs +++ b/Composite/C1Console/Security/UserGroupFacade.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Composite.Data; using Composite.Data.Types; @@ -46,6 +47,19 @@ public static IReadOnlyCollection GetUserGroupIds(string username) }); } + /// + + public static IEnumerable GetUserGroupActiveCultures(string username) + { + Verify.ArgumentNotNullOrEmpty(username, nameof(username)); + + return + from ugal in DataFacade.GetData() + join uugr in DataFacade.GetData() on ugal.UserGroupId equals uugr.UserGroupId + join user in DataFacade.GetData() on uugr.UserId equals user.Id + where user.Username == username + select CultureInfo.CreateSpecificCulture(ugal.CultureName); + } private static void OnDataChanged(object sender, StoreEventArgs storeEventArgs) diff --git a/Composite/C1Console/Users/UserSettings.cs b/Composite/C1Console/Users/UserSettings.cs index 927777e98b..93add6e56f 100644 --- a/Composite/C1Console/Users/UserSettings.cs +++ b/Composite/C1Console/Users/UserSettings.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Net; using Composite.C1Console.Security; -using System.Threading; +using Composite.Core.Caching; +using Composite.Data; namespace Composite.C1Console.Users @@ -109,7 +111,24 @@ public static CultureInfo ActiveLocaleCultureInfo /// public static CultureInfo GetCurrentActiveLocaleCultureInfo(string username) { - return _implementation.GetCurrentActiveLocaleCultureInfo(username); + var key = "CurrentActiveCulture" + username; + var cultureInfo = RequestLifetimeCache.TryGet(key); + + if (cultureInfo != null) return cultureInfo; + + cultureInfo = _implementation.GetCurrentActiveLocaleCultureInfo(username); + + var allowed = GetActiveLocaleCultureInfos(username, true); + if( !allowed.Contains(cultureInfo)) + { + cultureInfo = allowed.FirstOrDefault(); + } + + if (cultureInfo == null && PermissionsFacade.IsAdministrator(username)) cultureInfo = DataLocalizationFacade.DefaultLocalizationCulture; + + if (cultureInfo!=null && !RequestLifetimeCache.HasKey(key)) RequestLifetimeCache.Add(key, cultureInfo); + + return cultureInfo; } @@ -170,22 +189,24 @@ public static void RemoveActiveLocaleCultureInfo(string username, CultureInfo cu // Overload /// - /// This is an overload for GetActiveLocaleCultureInfos(string username) + /// This is an overload for GetActiveLocaleCultureInfos(string username, includeGroupAssignedCultures = true) /// using the current username. /// public static IEnumerable ActiveLocaleCultureInfos { get { - return GetActiveLocaleCultureInfos(UserSettings.Username); + return GetActiveLocaleCultureInfos(UserSettings.Username, true); } } /// - public static IEnumerable GetActiveLocaleCultureInfos(string username) + public static IEnumerable GetActiveLocaleCultureInfos(string username, bool includeGroupAssignedCultures = true) { - return _implementation.GetActiveLocaleCultureInfos(username); + return includeGroupAssignedCultures ? + _implementation.GetActiveLocaleCultureInfos(username).Union(UserGroupFacade.GetUserGroupActiveCultures(username)) : + _implementation.GetActiveLocaleCultureInfos(username); } diff --git a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs index 1fb49ab4aa..2e49d5ddf8 100644 --- a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs +++ b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs @@ -42,7 +42,14 @@ public void AuthorizeRequest(object sender, EventArgs e) if (adminRootRequest && UserValidationFacade.IsLoggedIn()) { - _dataScope = new DataScope(DataScopeIdentifier.Administrated, UserSettings.ActiveLocaleCultureInfo); + var activeLocale = UserSettings.ActiveLocaleCultureInfo; + + if (activeLocale==null) + { + throw new InvalidOperationException("No permissions for any content language exists for your user. Contact an administrator and ask for content language access."); + } + + _dataScope = new DataScope(DataScopeIdentifier.Administrated, activeLocale); if (!_consoleArtifactsInitialized) { From cea6f4cfa443d1c56b77980e20bad4e7bc4d92c8 Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 12 Jun 2019 17:49:04 +0200 Subject: [PATCH 23/44] Undoing changes in form login check / dialog. Missing language permission handled gracefully further in. --- .../scripts/source/top/core/KickStart.js | 391 +++++++++--------- Website/Composite/services/Login/Login.asmx | 8 - 2 files changed, 192 insertions(+), 207 deletions(-) diff --git a/Website/Composite/scripts/source/top/core/KickStart.js b/Website/Composite/scripts/source/top/core/KickStart.js index cec788b807..2498d3f6d6 100644 --- a/Website/Composite/scripts/source/top/core/KickStart.js +++ b/Website/Composite/scripts/source/top/core/KickStart.js @@ -2,15 +2,15 @@ * Kickstarting the entire shebang. */ var KickStart = new function () { - + var isLocalStoreReady = false; - var isQualified = Client.qualifies (); - + var isQualified = Client.qualifies(); + var DEFAULT_USERNAME = "admin"; var DEFAULT_PASSWORD = "123456"; - - if ( !isQualified ) { + + if (!isQualified) { document.location = "unsupported.aspx"; return; } @@ -19,49 +19,49 @@ var KickStart = new function () { * Fire on load! */ this.fireOnLoad = function () { - - // iPad IOS7 hack - if (Client.isPad && Client.isOS7 && window.innerHeight != document.documentElement.clientHeight) { - document.documentElement.style.height = window.innerHeight + "px"; - } - - Application.lock ( this ); - fileEventBroadcasterSubscriptions ( true ); - EventBroadcaster.subscribe ( BroadcastMessages.APPLICATION_SHUTDOWN, this ); - - SetupService = WebServiceProxy.createProxy ( Constants.URL_WSDL_SETUPSERVICE ); - ReadyService = WebServiceProxy.createProxy ( Constants.URL_WSDL_READYSERVICE ); - LoginService = WebServiceProxy.createProxy ( Constants.URL_WSDL_LOGINSERVICE ); - InstallationService = WebServiceProxy.createProxy(Constants.URL_WSDL_INSTALLSERVICE); - StringService = WebServiceProxy.createProxy(Constants.URL_WSDL_STRINGSERVICE); - - EventBroadcaster.broadcast(BroadcastMessages.APPLICATION_KICKSTART); - setTimeout(function () { - Persistance.initialize(); // NOTE: We are not using this stuff! - }, 0); + // iPad IOS7 hack + if (Client.isPad && Client.isOS7 && window.innerHeight != document.documentElement.clientHeight) { + document.documentElement.style.height = window.innerHeight + "px"; + } + + Application.lock(this); + fileEventBroadcasterSubscriptions(true); + EventBroadcaster.subscribe(BroadcastMessages.APPLICATION_SHUTDOWN, this); + + SetupService = WebServiceProxy.createProxy(Constants.URL_WSDL_SETUPSERVICE); + ReadyService = WebServiceProxy.createProxy(Constants.URL_WSDL_READYSERVICE); + LoginService = WebServiceProxy.createProxy(Constants.URL_WSDL_LOGINSERVICE); + InstallationService = WebServiceProxy.createProxy(Constants.URL_WSDL_INSTALLSERVICE); + StringService = WebServiceProxy.createProxy(Constants.URL_WSDL_STRINGSERVICE); + + EventBroadcaster.broadcast(BroadcastMessages.APPLICATION_KICKSTART); + + setTimeout(function () { + Persistance.initialize(); // NOTE: We are not using this stuff! + }, 0); }; /* * Indicate user just logged to console */ this.justLogged = false; - + /** * @implements {IBroadcastListener} * @param {string} broadcast */ - this.handleBroadcast = function ( broadcast ) { - - switch ( broadcast ) { - - case BroadcastMessages.PERSISTANCE_INITIALIZED : - kickStart ( broadcast ); + this.handleBroadcast = function (broadcast) { + + switch (broadcast) { + + case BroadcastMessages.PERSISTANCE_INITIALIZED: + kickStart(broadcast); break; - - case BroadcastMessages.APPLICATION_STARTUP : + + case BroadcastMessages.APPLICATION_STARTUP: // doStartUp (); hmmmm.... break; - + case BroadcastMessages.KEY_ENTER: if (bindingMap.decks != null) { var selecteddeck = bindingMap.decks.getSelectedDeckBinding(); @@ -70,129 +70,127 @@ var KickStart = new function () { case "logindeck": this.login(); break; - case "changepassworddeck": + case "changepassworddeck": this.changePassword(); break; default: } } - + } break; - - case BroadcastMessages.APPLICATION_LOGIN : + + case BroadcastMessages.APPLICATION_LOGIN: var appwindow = window.bindingMap.appwindow; //Workarrond for iPad - "div layout creashed on some reflex" if (Client.isPad) { appwindow.bindingElement.style.borderLeft = "1px solid #333"; } - appwindow.setURL ( "app.aspx" ); + appwindow.setURL("app.aspx"); break; - + case BroadcastMessages.APPLICATION_OPERATIONAL: showWorkbench(); - setTimeout(function() { + setTimeout(function () { StageBinding.bindingInstance.handleHash(window); }, 0); break; - - case BroadcastMessages.APPLICATION_SHUTDOWN : - if ( bindingMap.decks != null ) { - bindingMap.decks.select ( "shutdowndeck" ); + + case BroadcastMessages.APPLICATION_SHUTDOWN: + if (bindingMap.decks != null) { + bindingMap.decks.select("shutdowndeck"); } - bindingMap.cover.show (); + bindingMap.cover.show(); break; } } - + /** * File and unfile EventBroadcaster subscriptions. * @param {boolean} isSubscribe */ - function fileEventBroadcasterSubscriptions ( isSubscribe ) { - - new List ([ - + function fileEventBroadcasterSubscriptions(isSubscribe) { + + new List([ + BroadcastMessages.PERSISTANCE_INITIALIZED, BroadcastMessages.APPLICATION_STARTUP, BroadcastMessages.APPLICATION_LOGIN, BroadcastMessages.APPLICATION_OPERATIONAL - - ]).each ( - function ( broadcast ) { - if ( isSubscribe ) { - EventBroadcaster.subscribe ( broadcast, KickStart ); + + ]).each( + function (broadcast) { + if (isSubscribe) { + EventBroadcaster.subscribe(broadcast, KickStart); } else { - EventBroadcaster.unsubscribe ( broadcast, KickStart ); + EventBroadcaster.unsubscribe(broadcast, KickStart); } } ); } - + /** * Freeze storyboard until Localstore initialize. * If not registered, show registration. Otherwise show login. * @param {string} broadcast */ - function kickStart ( broadcast ) { - - switch ( broadcast ) { - case BroadcastMessages.PERSISTANCE_INITIALIZED : + function kickStart(broadcast) { + + switch (broadcast) { + case BroadcastMessages.PERSISTANCE_INITIALIZED: isLocalStoreReady = true; break; } - - if ( isLocalStoreReady ) { - if ( bindingMap.decks != null && LoginService.IsLoggedIn ( true )) { - accessGranted (); + + if (isLocalStoreReady) { + if (bindingMap.decks != null && LoginService.IsLoggedIn(true)) { + accessGranted(); } else { - if ( bindingMap.decks != null ) { - showLogin (); + if (bindingMap.decks != null) { + showLogin(); } else { - showWelcome (); + showWelcome(); } } } splashScreenData(); } - + /** * Splash screen data. */ - function splashScreenData () { + function splashScreenData() { var elems = document.getElementsByClassName("js-applicationname"); - for(var index = 0; index < elems.length; ++index) - { + for (var index = 0; index < elems.length; ++index) { elems[index].innerHTML = Installation.applicationName; } } - + /* * Show welcome screens on first time startup. */ - function showWelcome () { - - Application.unlock ( KickStart ); - if ( window.Welcome != null ) { - Welcome.test (); + function showWelcome() { + + Application.unlock(KickStart); + if (window.Welcome != null) { + Welcome.test(); } } - + /* * Show login screen. */ - function showLogin () { - - EventBroadcaster.subscribe ( BroadcastMessages.KEY_ENTER, KickStart ); - Application.unlock ( KickStart ); - bindingMap.decks.select ( "logindeck" ); - - setTimeout ( function () { + function showLogin() { + + EventBroadcaster.subscribe(BroadcastMessages.KEY_ENTER, KickStart); + Application.unlock(KickStart); + bindingMap.decks.select("logindeck"); + + setTimeout(function () { if (Application.isLocalHost) { - if(Application.isDeveloperMode || Client.isPerformanceTest) - { + if (Application.isDeveloperMode || Client.isPerformanceTest) { DataManager.getDataBinding("username").setValue(DEFAULT_USERNAME); DataManager.getDataBinding("password").setValue(DEFAULT_PASSWORD); } @@ -201,42 +199,42 @@ var KickStart = new function () { KickStart.login(); } } - setTimeout ( function () { - DataManager.getDataBinding ( "username" ).focus (); - }, 250 ); - }, 0 ); + setTimeout(function () { + DataManager.getDataBinding("username").focus(); + }, 250); + }, 0); } - + /** * When registered, monitor the servers readystate and continue to login screen when done. */ - function watchProgress () { - - window.progressOnRegistrationInterval = window.setInterval ( function () { - if ( ReadyService.IsServerReady ( true )) { - window.clearInterval ( window.progressOnRegistrationInterval ); + function watchProgress() { + + window.progressOnRegistrationInterval = window.setInterval(function () { + if (ReadyService.IsServerReady(true)) { + window.clearInterval(window.progressOnRegistrationInterval); window.progressOnRegistrationInterval = null; - splashScreenData (); - showLogin (); + splashScreenData(); + showLogin(); } - }, 2000 ); + }, 2000); } - + /** * Show it. */ - function showWorkbench () { - - setTimeout ( function () { - bindingMap.cover.hide (); - fileEventBroadcasterSubscriptions ( false ); - Application.unlock ( KickStart ); - }, PageBinding.TIMEOUT ); + function showWorkbench() { + + setTimeout(function () { + bindingMap.cover.hide(); + fileEventBroadcasterSubscriptions(false); + Application.unlock(KickStart); + }, PageBinding.TIMEOUT); } this.changePassword = function () { - + if (bindingMap.toppage.validateAllDataBindings()) { var username = DataManager.getDataBinding("username").getResult(); @@ -256,7 +254,7 @@ var KickStart = new function () { alert(result.getFaultString()); } else { if (result.length == 0) { - setTimeout(function() { + setTimeout(function () { top.window.location.reload(true); }, 0); } else { @@ -270,7 +268,7 @@ var KickStart = new function () { } } else { - + this.showPasswordErrors([Resolver.resolve("${string:Composite.C1Console.Users:ChangePasswordForm.ConfirmationPasswordMimatch}")]); } } @@ -281,14 +279,14 @@ var KickStart = new function () { var errorsElement = document.getElementById("passworderror"); errorsElement.innerHTML = ""; - errors.each(function(error) { + errors.each(function (error) { var errorElement = document.createElement("div"); errorElement.textContent = error; errorElement.className = "text-error text-sm"; errorsElement.appendChild(errorElement); }); - + errorsElement.style.display = "block"; @@ -298,12 +296,12 @@ var KickStart = new function () { handleAction: function (action) { document.getElementById("passworderror").style.display = "none"; action.target.removeActionListener( - Binding.ACTION_DIRTY, handler + Binding.ACTION_DIRTY, handler ); } } bindingMap.passwordfields.addActionListener( - Binding.ACTION_DIRTY, handler + Binding.ACTION_DIRTY, handler ); DataManager.getDataBinding("passwordold").clean(); @@ -312,31 +310,31 @@ var KickStart = new function () { } - + /** * Note that we disable SOAP debugging during login. * Note that we may be able to do something intelligent with the SOAP response. * Note that we didn't manage to come up with intelligent handling of the SOAP response. */ this.login = function () { - - Application.lock ( KickStart ); // unlocked by showWorkbench or if fields don't validate - + + Application.lock(KickStart); // unlocked by showWorkbench or if fields don't validate + /* * The timeout is here to block GUI with wait cursor. */ - setTimeout ( function () { - - if ( bindingMap.toppage.validateAllDataBindings ()) { - KickStart.doLogin ( - DataManager.getDataBinding ( "username" ).getResult (), - DataManager.getDataBinding ( "password" ).getResult () + setTimeout(function () { + + if (bindingMap.toppage.validateAllDataBindings()) { + KickStart.doLogin( + DataManager.getDataBinding("username").getResult(), + DataManager.getDataBinding("password").getResult() ); } else { - Application.unlock ( KickStart ); + Application.unlock(KickStart); } - - }, 25 ); + + }, 25); } /** @@ -344,73 +342,68 @@ var KickStart = new function () { * @param {String} username * @param {String} password */ - this.doLogin = function ( username, password ) { - + this.doLogin = function (username, password) { + var wasEnabled = WebServiceProxy.isLoggingEnabled; WebServiceProxy.isLoggingEnabled = false; WebServiceProxy.isFaultHandler = false; - + var isAllowed = false; var isChangePasswordRequired = false; - var result = LoginService.ValidateAndLogin ( username, password ); - if ( result instanceof SOAPFault ) { - alert ( result.getFaultString ()); + var result = LoginService.ValidateAndLogin(username, password); + if (result instanceof SOAPFault) { + alert(result.getFaultString()); } else { - if (result == "lockedAfterMaxAttempts") { - // TODO: unhardcode - alert("The account was locked after maximum login attempts. Please contact administrator."); - } - - if (result == "lockedByAnAdministrator") { - // TODO: unhardcode - alert("The account was locked by an administrator."); - } + if (result == "lockedAfterMaxAttempts") { + // TODO: unhardcode + alert("The account was locked after maximum login attempts. Please contact administrator."); + } - if (result == "passwordUpdateRequired") { - isChangePasswordRequired = true; + if (result == "lockedByAnAdministrator") { + // TODO: unhardcode + alert("The account was locked by an administrator."); + } - } + if (result == "passwordUpdateRequired") { + isChangePasswordRequired = true; - if (result == "loginHasNoLanguageAccess") { - // TODO: unhardcode - alert("The login has no Data Language permissions. Please contact an administrator and ask for a data language to be assigned to your user permissions."); } if (result == "success") { - isAllowed = true; - } + isAllowed = true; + } } if (isChangePasswordRequired) { changePasswordRequired(); - }else if ( isAllowed ) { - EventBroadcaster.unsubscribe ( BroadcastMessages.KEY_ENTER, KickStart ); + } else if (isAllowed) { + EventBroadcaster.unsubscribe(BroadcastMessages.KEY_ENTER, KickStart); this.justLogged = true; - accessGranted (); + accessGranted(); } else { - Application.unlock ( KickStart ); - if ( bindingMap.decks != null ) { // on Welcome we may get trapped here! - accesssDenied (); + Application.unlock(KickStart); + if (bindingMap.decks != null) { // on Welcome we may get trapped here! + accesssDenied(); } } WebServiceProxy.isFaultHandler = true; - if ( wasEnabled ) { + if (wasEnabled) { WebServiceProxy.isLoggingEnabled = true; } } - + /** * Access granted. */ - function accessGranted () { - setTimeout ( function () { - if ( bindingMap.decks != null ) { - bindingMap.decks.select ( "loadingdeck" ); + function accessGranted() { + setTimeout(function () { + if (bindingMap.decks != null) { + bindingMap.decks.select("loadingdeck"); } - setTimeout ( function () { - Application.login (); - }, 0 ); - }, 0 ); + setTimeout(function () { + Application.login(); + }, 0); + }, 0); } /** @@ -421,12 +414,12 @@ var KickStart = new function () { setTimeout(function () { Application.unlock(KickStart); if (bindingMap.decks != null) { - bindingMap.decks.select("changepassworddeck"); + bindingMap.decks.select("changepassworddeck"); bindingMap.cover.attachClassName("widesplash"); setTimeout(function () { - var passwordexpired = document.getElementById("passwordexpired"); - passwordexpired.textContent = passwordexpired.textContent.replace("{0}", Installation.passwordExpirationTimeInDays); + var passwordexpired = document.getElementById("passwordexpired"); + passwordexpired.textContent = passwordexpired.textContent.replace("{0}", Installation.passwordExpirationTimeInDays); DataManager.getDataBinding("usernameold").setValue(DataManager.getDataBinding("username").getResult()); DataManager.getDataBinding("passwordold").focus(); @@ -435,49 +428,49 @@ var KickStart = new function () { } }, 25); } - + /** * Access denied. */ - function accesssDenied () { - - var username = DataManager.getDataBinding ( "username" ); - var password = DataManager.getDataBinding ( "password" ); - - username.blur (); - password.blur (); - username.setValue ( "" ); - password.setValue ( "" ); - username.clean (); - password.clean (); - username.focus (); - - document.getElementById ( "loginerror" ).style.display = "block"; - + function accesssDenied() { + + var username = DataManager.getDataBinding("username"); + var password = DataManager.getDataBinding("password"); + + username.blur(); + password.blur(); + username.setValue(""); + password.setValue(""); + username.clean(); + password.clean(); + username.focus(); + + document.getElementById("loginerror").style.display = "block"; + var handler = { - handleAction : function ( action ) { - document.getElementById ( "loginerror" ).style.display = "none"; - action.target.removeActionListener ( - Binding.ACTION_DIRTY, handler + handleAction: function (action) { + document.getElementById("loginerror").style.display = "none"; + action.target.removeActionListener( + Binding.ACTION_DIRTY, handler ); } } - bindingMap.loginfields.addActionListener ( - Binding.ACTION_DIRTY, handler + bindingMap.loginfields.addActionListener( + Binding.ACTION_DIRTY, handler ); } - + /* * Fire on load! */ - WindowManager.fireOnLoad ( this ); - + WindowManager.fireOnLoad(this); + /* * Non-qualified browsers would run into a javascript * error in the UpdateManager when switching to the * not supported page. Let's disable the UpdateManager. */ - if ( !isQualified ) { + if (!isQualified) { UpdateManager.isEnabled = false; } } diff --git a/Website/Composite/services/Login/Login.asmx b/Website/Composite/services/Login/Login.asmx index 6ada8b541f..ac8a4576f7 100644 --- a/Website/Composite/services/Login/Login.asmx +++ b/Website/Composite/services/Login/Login.asmx @@ -23,14 +23,6 @@ namespace Composite.Services { var result = UserValidationFacade.FormValidateUser(username, password); - var activeContentCulture = UserSettings.ActiveLocaleCultureInfo; - - if (activeContentCulture==null && result == LoginResult.Success) - { - UserValidationFacade.Logout(); - return "loginHasNoLanguageAccess"; - } - switch (result) { case LoginResult.Success: From 817061e4626eb698b1bfc275222b14884d15c4f9 Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 12 Jun 2019 17:51:04 +0200 Subject: [PATCH 24/44] Undoing language permission check on all console request, such a case is now handled gracefully forther in. --- .../AdministrativeDataScopeSetterHttpModule.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs index 2e49d5ddf8..1fb49ab4aa 100644 --- a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs +++ b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs @@ -42,14 +42,7 @@ public void AuthorizeRequest(object sender, EventArgs e) if (adminRootRequest && UserValidationFacade.IsLoggedIn()) { - var activeLocale = UserSettings.ActiveLocaleCultureInfo; - - if (activeLocale==null) - { - throw new InvalidOperationException("No permissions for any content language exists for your user. Contact an administrator and ask for content language access."); - } - - _dataScope = new DataScope(DataScopeIdentifier.Administrated, activeLocale); + _dataScope = new DataScope(DataScopeIdentifier.Administrated, UserSettings.ActiveLocaleCultureInfo); if (!_consoleArtifactsInitialized) { From 2970fab287e539e9757231137608015b1879089b Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 12 Jun 2019 17:57:49 +0200 Subject: [PATCH 25/44] Making PageElementProvider behave friendly in case user has no data language permissions. --- .../PageElementProvider/PageElementProvider.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index 1fc57b4d6c..18a0b34723 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -106,14 +106,27 @@ public ElementProviderContext Context public IEnumerable GetRoots(SearchToken searchToken) { + EntityToken entityToken = new PageElementProviderEntityToken(_context.ProviderName); + + if (!DataLocalizationFacade.ActiveLocalizationCultures.Contains(UserSettings.ActiveLocaleCultureInfo)) + { + yield return new Element(_context.CreateElementHandle(entityToken)) + { + VisualData = new ElementVisualizedData + { + Label = "No website access: Missing permissions to any Data Language", + Icon = PageElementProvider.DeactivateLocalization + } + }; + yield break; + } + int pages; using (new DataScope(DataScopeIdentifier.Administrated)) { pages = PageServices.GetChildrenCount(Guid.Empty); } - EntityToken entityToken = new PageElementProviderEntityToken(_context.ProviderName); - var dragAndDropInfo = new ElementDragAndDropInfo(); dragAndDropInfo.AddDropType(typeof(IPage)); dragAndDropInfo.SupportsIndexedPosition = true; @@ -301,6 +314,7 @@ public IEnumerable GetForeignRoots(SearchToken searchToken) public IEnumerable GetForeignChildren(EntityToken entityToken, SearchToken searchToken) { if (entityToken is DataEntityToken dataEntityToken && dataEntityToken.Data == null) return Array.Empty(); + if (!DataLocalizationFacade.ActiveLocalizationCultures.Contains(UserSettings.ActiveLocaleCultureInfo)) return Enumerable.Empty(); if (entityToken is AssociatedDataElementProviderHelperEntityToken associatedData) { From c871655bec495fbd166e17dae8f3e603a38d634f Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 12 Jun 2019 18:07:15 +0200 Subject: [PATCH 26/44] Data Language mermission check will now (when no permissions exists) yield InvariantCulture - this will satisfy all uses, except when accessing data (which is the goal --- Composite/C1Console/Users/UserSettings.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/Composite/C1Console/Users/UserSettings.cs b/Composite/C1Console/Users/UserSettings.cs index 93add6e56f..4c6120646d 100644 --- a/Composite/C1Console/Users/UserSettings.cs +++ b/Composite/C1Console/Users/UserSettings.cs @@ -112,23 +112,16 @@ public static CultureInfo ActiveLocaleCultureInfo public static CultureInfo GetCurrentActiveLocaleCultureInfo(string username) { var key = "CurrentActiveCulture" + username; - var cultureInfo = RequestLifetimeCache.TryGet(key); + if (RequestLifetimeCache.HasKey(key)) return RequestLifetimeCache.TryGet(key); - if (cultureInfo != null) return cultureInfo; + var cultureInfo = _implementation.GetCurrentActiveLocaleCultureInfo(username); + var allowedCultures = GetActiveLocaleCultureInfos(username, true); - cultureInfo = _implementation.GetCurrentActiveLocaleCultureInfo(username); + if( !allowedCultures.Contains(cultureInfo)) cultureInfo = allowedCultures.FirstOrDefault(); - var allowed = GetActiveLocaleCultureInfos(username, true); - if( !allowed.Contains(cultureInfo)) - { - cultureInfo = allowed.FirstOrDefault(); - } - - if (cultureInfo == null && PermissionsFacade.IsAdministrator(username)) cultureInfo = DataLocalizationFacade.DefaultLocalizationCulture; - - if (cultureInfo!=null && !RequestLifetimeCache.HasKey(key)) RequestLifetimeCache.Add(key, cultureInfo); + if (cultureInfo != null && !RequestLifetimeCache.HasKey(key)) RequestLifetimeCache.Add(key, cultureInfo); - return cultureInfo; + return cultureInfo ?? CultureInfo.InvariantCulture; } From e2148cf1949a2d43080f109b5855a6b34d87a1fa Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 12 Jun 2019 19:29:57 +0200 Subject: [PATCH 27/44] Returning empty elements for PageElementProvider GetChildren when called with unsupported data language --- .../ElementProviders/PageElementProvider/PageElementProvider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index 18a0b34723..afd8c83a45 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -281,6 +281,8 @@ var pageType in public IEnumerable GetChildren(EntityToken entityToken, SearchToken searchToken) { + if (!DataLocalizationFacade.ActiveLocalizationCultures.Contains(UserSettings.ActiveLocaleCultureInfo)) return Enumerable.Empty(); + if (entityToken is AssociatedDataElementProviderHelperEntityToken associatedData) { return _pageAssociatedHelper.GetChildren(associatedData, false); From 1d0e228f862bee950faec104dda652d8239b59ad Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 13 Jun 2019 18:06:41 +0200 Subject: [PATCH 28/44] Bumping version moniker to 6.7 --- Composite/Properties/SharedAssemblyInfo.cs | 8 ++++---- .../ReleaseBuild.Composite.config.changeHistory.txt | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Composite/Properties/SharedAssemblyInfo.cs b/Composite/Properties/SharedAssemblyInfo.cs index 9d8c21ed56..c83dfe9766 100644 --- a/Composite/Properties/SharedAssemblyInfo.cs +++ b/Composite/Properties/SharedAssemblyInfo.cs @@ -2,15 +2,15 @@ // General Information about the assemblies Composite and Composite.Workflows #if !InternalBuild -[assembly: AssemblyTitle("C1 CMS 6.6")] +[assembly: AssemblyTitle("C1 CMS 6.7")] #else -[assembly: AssemblyTitle("C1 CMS 6.6 (Internal Build)")] +[assembly: AssemblyTitle("C1 CMS 6.7 (Internal Build)")] #endif [assembly: AssemblyCompany("Orckestra Inc")] [assembly: AssemblyProduct("C1 CMS")] -[assembly: AssemblyCopyright("Copyright © Orckestra Inc 2018")] +[assembly: AssemblyCopyright("Copyright © Orckestra Inc 2019")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("6.6.*")] +[assembly: AssemblyVersion("6.7.*")] diff --git a/Website/App_Data/Composite/ReleaseBuild.Composite.config.changeHistory.txt b/Website/App_Data/Composite/ReleaseBuild.Composite.config.changeHistory.txt index cce2ab8481..9b5c5997ea 100644 --- a/Website/App_Data/Composite/ReleaseBuild.Composite.config.changeHistory.txt +++ b/Website/App_Data/Composite/ReleaseBuild.Composite.config.changeHistory.txt @@ -170,4 +170,7 @@ Changes in 6.0 or later Changes in 6.6 or later: -Added to loggingConfiguration/listeners - -Added to loggingConfiguration/specialSources/allEvents/listeners \ No newline at end of file + -Added to loggingConfiguration/specialSources/allEvents/listeners + + Changes in 6.7 or later: + - n/a \ No newline at end of file From 6699b1d8202e974495692ec69d81f1ffadffea6f Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 14 Jun 2019 12:11:29 +0200 Subject: [PATCH 29/44] When adding user to group as part of setup flow, also assign language access to said group. This fragment installer is used for Setup and ensure first user has access out of the box. --- .../UserGroupUserAdderFragmentInstaller.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Composite/Core/PackageSystem/PackageFragmentInstallers/UserGroupUserAdderFragmentInstaller.cs b/Composite/Core/PackageSystem/PackageFragmentInstallers/UserGroupUserAdderFragmentInstaller.cs index d92644532a..fb09a8820a 100644 --- a/Composite/Core/PackageSystem/PackageFragmentInstallers/UserGroupUserAdderFragmentInstaller.cs +++ b/Composite/Core/PackageSystem/PackageFragmentInstallers/UserGroupUserAdderFragmentInstaller.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Composite.Core.Linq; @@ -9,7 +9,7 @@ namespace Composite.Core.PackageSystem.PackageFragmentInstallers { /// - /// Adds all the users to the specified user group. Used in starter site packages. + /// Adds all the users to the specified user group. Assign language permissions to those groups. Used in starter site packages. /// /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] @@ -52,6 +52,14 @@ public override IEnumerable Install() userUserGroupRelation.UserGroupId = userGroup.Id; DataFacade.AddNew(userUserGroupRelation); } + + foreach (var cultureInfo in DataLocalizationFacade.ActiveLocalizationCultures) + { + var userGroupActiveLocale = DataFacade.BuildNew(); + userGroupActiveLocale.UserGroupId = userGroup.Id; + userGroupActiveLocale.CultureName = cultureInfo.Name; + DataFacade.AddNew(userGroupActiveLocale); + } } yield break; From 2d720b193c5559edb5b1f574ccd9bc7295b031f9 Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 3 Jul 2019 15:27:07 +0200 Subject: [PATCH 30/44] Fixing issue where page data (like blog posts) expose other blog pages as parent when resolving ancestors. Negative effect is blogs from one subsite can appear in searches for another subsite. --- Composite/C1Console/Trees/RootTreeNode.cs | 32 ++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Composite/C1Console/Trees/RootTreeNode.cs b/Composite/C1Console/Trees/RootTreeNode.cs index c8ca95d4e5..06f0deedf6 100644 --- a/Composite/C1Console/Trees/RootTreeNode.cs +++ b/Composite/C1Console/Trees/RootTreeNode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Composite.C1Console.Elements; @@ -67,16 +67,30 @@ public override IEnumerable GetEntityTokens(EntityToken childEntity private AncestorMatch GetAncestorFromParentFilter(TreeNodeDynamicContext dynamicContext) { - var dataNode = dynamicContext.CurrentTreeNode as DataElementsTreeNode; - if(dataNode == null) return null; - - var parentIdFilter = dynamicContext.CurrentTreeNode.FilterNodes.OfType().FirstOrDefault(); - if (parentIdFilter == null) return null; + var treeNode = dynamicContext.CurrentTreeNode; + if (treeNode is DataElementsTreeNode) + { + var parentIdFilter = treeNode.FilterNodes.OfType().FirstOrDefault(); + if (parentIdFilter == null) return null; + + Type ancestorType = parentIdFilter.ParentFilterType; + object key = parentIdFilter.FindParentKeyValue(dynamicContext); + + return key == null ? null : new AncestorMatch { InterfaceType = ancestorType, KeyValue = key}; + } - Type ancestorType = parentIdFilter.ParentFilterType; - object key = parentIdFilter.FindParentKeyValue(dynamicContext); + if (treeNode is DataFolderElementsTreeNode + && dynamicContext.CurrentEntityToken is TreeDataFieldGroupingElementEntityToken groupEntityToken + && groupEntityToken.ChildGeneratingDataElementsReferenceValue != null) + { + return new AncestorMatch + { + InterfaceType = groupEntityToken.ChildGeneratingDataElementsReferenceType, + KeyValue = groupEntityToken.ChildGeneratingDataElementsReferenceValue + }; + } - return key == null ? null : new AncestorMatch { InterfaceType = ancestorType, KeyValue = key}; + return null; } From a09632e645b06282129c0b94536d87d53317366b Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 4 Jul 2019 14:47:31 +0200 Subject: [PATCH 31/44] Fix issue where C1 Package containing IPage and other content (data) fail to install as part of setup (due to 'add language' not affecting data stores before next "Flush"). AddLocale method already had parameter for requesting such a flush, but was ignored. The parameter is now respected. --- Composite/Core/Localization/LocalizationFacade.cs | 7 ++++++- Composite/Core/WebClient/Setup/SetupServiceFacade.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Composite/Core/Localization/LocalizationFacade.cs b/Composite/Core/Localization/LocalizationFacade.cs index f92ad74eb5..575bfa7229 100644 --- a/Composite/Core/Localization/LocalizationFacade.cs +++ b/Composite/Core/Localization/LocalizationFacade.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -189,6 +189,11 @@ internal static void AddLocale(CultureInfo cultureInfo, string urlMappingName, b } DynamicTypeManager.AddLocale(cultureInfo); + + if (makeFlush) + { + C1Console.Events.GlobalEventSystemFacade.FlushTheSystem(false); + } } diff --git a/Composite/Core/WebClient/Setup/SetupServiceFacade.cs b/Composite/Core/WebClient/Setup/SetupServiceFacade.cs index b3fa9de824..53ab66410c 100644 --- a/Composite/Core/WebClient/Setup/SetupServiceFacade.cs +++ b/Composite/Core/WebClient/Setup/SetupServiceFacade.cs @@ -114,7 +114,7 @@ public static bool SetUp(string setupDescriptionXml, string username, string pas ApplicationLevelEventHandlers.ApplicationStartInitialize(); Log.LogInformation(VerboseLogTitle, "Creating first locale: " + language); - LocalizationFacade.AddLocale(locale, "", true, false, true); + LocalizationFacade.AddLocale(locale, "", true, true, true); Log.LogInformation(VerboseLogTitle, "Creating first user: " + username); From 27066ca14940ea157b5ab6db8879ee80ba4aa4a4 Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 4 Jul 2019 14:47:57 +0200 Subject: [PATCH 32/44] Log type fix (textual) --- .../DataProviders/XmlDataProvider/XmlDataProvider_Stores.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Composite/Plugins/Data/DataProviders/XmlDataProvider/XmlDataProvider_Stores.cs b/Composite/Plugins/Data/DataProviders/XmlDataProvider/XmlDataProvider_Stores.cs index f75d049159..ef10df495c 100644 --- a/Composite/Plugins/Data/DataProviders/XmlDataProvider/XmlDataProvider_Stores.cs +++ b/Composite/Plugins/Data/DataProviders/XmlDataProvider/XmlDataProvider_Stores.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Configuration; using System.Globalization; @@ -115,7 +115,6 @@ public void AddLocale(CultureInfo cultureInfo) } - public void RemoveLocale(CultureInfo cultureInfo) { XmlDataProviderDocumentCache.ClearCache(); @@ -148,7 +147,7 @@ private void InitializeExistingStores() { if (!dataTypes.TryGetValue(dataTypeDescriptor.DataTypeId, out interfaceType) || interfaceType == null) { - Log.LogWarning(LogTitle, "The data interface type '{0}' does not exists and is not code generated. It will not be usable", dataTypeDescriptor.TypeManagerTypeName); + Log.LogWarning(LogTitle, "The data interface type '{0}' does not exist and is not code generated. It will not be usable", dataTypeDescriptor.TypeManagerTypeName); continue; } From 4422d5d56c969aff93c21b24b02cc9c8ce3deeae Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 4 Jul 2019 14:49:20 +0200 Subject: [PATCH 33/44] Fixing bug with no current repro, but this is a pretty obvious copy/paste bug. --- Composite/Data/ProcessControlled/ProcessControllerFacade.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Composite/Data/ProcessControlled/ProcessControllerFacade.cs b/Composite/Data/ProcessControlled/ProcessControllerFacade.cs index bd5849e97e..cad1b2a006 100644 --- a/Composite/Data/ProcessControlled/ProcessControllerFacade.cs +++ b/Composite/Data/ProcessControlled/ProcessControllerFacade.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -249,7 +249,7 @@ private static void OnDataBuildNew(object sender, DataEventArgs dataEventArgs) foreach (IPublishControlledAuxiliary publishControlledAuxiliary in publishControlledAuxiliaries) { - publishControlledAuxiliary.OnAfterDataUpdated(dataEventArgs.Data); + publishControlledAuxiliary.OnAfterBuildNew(dataEventArgs.Data); } } } From 04bf53be915a33f71f31f31d8911b12edf1541bd Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 4 Jul 2019 16:10:39 +0200 Subject: [PATCH 34/44] Fixing issue where Element Tree generation require an active user. Manifested itself by failing to init Page Tree on first load after setup. --- .../DataTypeDescriptorFormsHelper.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Composite/Data/DynamicTypes/DataTypeDescriptorFormsHelper.cs b/Composite/Data/DynamicTypes/DataTypeDescriptorFormsHelper.cs index b528cb6284..f5dac54791 100644 --- a/Composite/Data/DynamicTypes/DataTypeDescriptorFormsHelper.cs +++ b/Composite/Data/DynamicTypes/DataTypeDescriptorFormsHelper.cs @@ -265,27 +265,34 @@ public Dictionary GetNewBindings() private static Dictionary GetAvailablePublishingFlowTransitions(EntityToken entityToken) { - var transitionNames = new Dictionary + if(UserValidationFacade.IsLoggedIn()) + { + var transitionNames = new Dictionary { {GenericPublishProcessController.Draft, LocalizationFiles.Composite_Management.PublishingStatus_draft}, {GenericPublishProcessController.AwaitingApproval, LocalizationFiles.Composite_Management.PublishingStatus_awaitingApproval} }; - var username = UserValidationFacade.GetUsername(); - var userPermissionDefinitions = PermissionTypeFacade.GetUserPermissionDefinitions(username); - var userGroupPermissionDefinition = PermissionTypeFacade.GetUserGroupPermissionDefinitions(username); - var currentPermissionTypes = PermissionTypeFacade.GetCurrentPermissionTypes(UserValidationFacade.GetUserToken(), entityToken, userPermissionDefinitions, userGroupPermissionDefinition); - foreach (var permissionType in currentPermissionTypes) - { - if (GenericPublishProcessController.AwaitingPublicationActionPermissionType.Contains(permissionType)) + var username = UserValidationFacade.GetUsername(); + var userPermissionDefinitions = PermissionTypeFacade.GetUserPermissionDefinitions(username); + var userGroupPermissionDefinition = PermissionTypeFacade.GetUserGroupPermissionDefinitions(username); + var currentPermissionTypes = PermissionTypeFacade.GetCurrentPermissionTypes(UserValidationFacade.GetUserToken(), entityToken, userPermissionDefinitions, userGroupPermissionDefinition); + foreach (var permissionType in currentPermissionTypes) { - transitionNames.Add(GenericPublishProcessController.AwaitingPublication, - LocalizationFiles.Composite_Management.PublishingStatus_awaitingPublication); - break; + if (GenericPublishProcessController.AwaitingPublicationActionPermissionType.Contains(permissionType)) + { + transitionNames.Add(GenericPublishProcessController.AwaitingPublication, + LocalizationFiles.Composite_Management.PublishingStatus_awaitingPublication); + break; + } } - } - return transitionNames; + return transitionNames; + } + else + { + return new Dictionary(); + } } From c8787b3c41a382f286483fb23799bb0fb9a3c6d7 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 16 Jul 2019 14:12:55 +0200 Subject: [PATCH 35/44] Removing length limitation from IHostnameBinding.Aliases property --- Composite/Data/Types/IHostnameBinding.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/Data/Types/IHostnameBinding.cs b/Composite/Data/Types/IHostnameBinding.cs index fdada4a497..c68390ff33 100644 --- a/Composite/Data/Types/IHostnameBinding.cs +++ b/Composite/Data/Types/IHostnameBinding.cs @@ -48,7 +48,7 @@ public interface IHostnameBinding : IData string PageNotFoundUrl { get; set; } /// - [StoreFieldType(PhysicalStoreFieldType.String, 512, IsNullable = true)] + [StoreFieldType(PhysicalStoreFieldType.LargeString)] [ImmutableFieldId("{80C298F2-F493-465D-9F28-4F50DD0C03D5}")] string Aliases { get; set; } From b54465bae0429e50838fafa434d2c0f6b550d563 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 25 Jul 2019 10:55:27 +0200 Subject: [PATCH 36/44] Making IHostnameBinding.Aliases nullable for backward compatibility --- Composite/Data/Types/IHostnameBinding.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/Data/Types/IHostnameBinding.cs b/Composite/Data/Types/IHostnameBinding.cs index c68390ff33..79b8abf961 100644 --- a/Composite/Data/Types/IHostnameBinding.cs +++ b/Composite/Data/Types/IHostnameBinding.cs @@ -48,7 +48,7 @@ public interface IHostnameBinding : IData string PageNotFoundUrl { get; set; } /// - [StoreFieldType(PhysicalStoreFieldType.LargeString)] + [StoreFieldType(PhysicalStoreFieldType.LargeString, IsNullable = true)] [ImmutableFieldId("{80C298F2-F493-465D-9F28-4F50DD0C03D5}")] string Aliases { get; set; } From 300c99fe8d0ec075dc528f995e01ee85eae100fa Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 1 Aug 2019 17:26:50 +0200 Subject: [PATCH 37/44] Fixing an exception when content tree wouldn't load --- .../Scheduling/PublishControlledHelper.cs | 9 +++++++-- .../PageElementProvider/PageElementProvider.cs | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Composite.Workflows/C1Console/Scheduling/PublishControlledHelper.cs b/Composite.Workflows/C1Console/Scheduling/PublishControlledHelper.cs index af424ddb08..ef921b4b9a 100644 --- a/Composite.Workflows/C1Console/Scheduling/PublishControlledHelper.cs +++ b/Composite.Workflows/C1Console/Scheduling/PublishControlledHelper.cs @@ -87,11 +87,16 @@ public static void ReloadPageElementInConsole(IPage page) public static void ReloadDataElementInConsole(DataEntityToken dataEntityToken) { + if (dataEntityToken == null) throw new ArgumentNullException(nameof(dataEntityToken)); + var parentEntityTokens = AuxiliarySecurityAncestorFacade.GetParents(dataEntityToken); - foreach (var parentEntityToken in parentEntityTokens) + if (parentEntityTokens != null) { - ConsoleMessageQueueFacade.Enqueue(new RefreshTreeMessageQueueItem { EntityToken = parentEntityToken }, null); + foreach (var parentEntityToken in parentEntityTokens) + { + ConsoleMessageQueueFacade.Enqueue(new RefreshTreeMessageQueueItem { EntityToken = parentEntityToken }, null); + } } } diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index afd8c83a45..ae684b94a0 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -108,7 +108,8 @@ public IEnumerable GetRoots(SearchToken searchToken) { EntityToken entityToken = new PageElementProviderEntityToken(_context.ProviderName); - if (!DataLocalizationFacade.ActiveLocalizationCultures.Contains(UserSettings.ActiveLocaleCultureInfo)) + if (UserValidationFacade.IsLoggedIn() + && !DataLocalizationFacade.ActiveLocalizationCultures.Contains(UserSettings.ActiveLocaleCultureInfo)) { yield return new Element(_context.CreateElementHandle(entityToken)) { @@ -281,7 +282,11 @@ var pageType in public IEnumerable GetChildren(EntityToken entityToken, SearchToken searchToken) { - if (!DataLocalizationFacade.ActiveLocalizationCultures.Contains(UserSettings.ActiveLocaleCultureInfo)) return Enumerable.Empty(); + if (UserValidationFacade.IsLoggedIn() + && !DataLocalizationFacade.ActiveLocalizationCultures.Contains(UserSettings.ActiveLocaleCultureInfo)) + { + return Enumerable.Empty(); + } if (entityToken is AssociatedDataElementProviderHelperEntityToken associatedData) { @@ -316,7 +321,11 @@ public IEnumerable GetForeignRoots(SearchToken searchToken) public IEnumerable GetForeignChildren(EntityToken entityToken, SearchToken searchToken) { if (entityToken is DataEntityToken dataEntityToken && dataEntityToken.Data == null) return Array.Empty(); - if (!DataLocalizationFacade.ActiveLocalizationCultures.Contains(UserSettings.ActiveLocaleCultureInfo)) return Enumerable.Empty(); + if (UserValidationFacade.IsLoggedIn() + && !DataLocalizationFacade.ActiveLocalizationCultures.Contains(UserSettings.ActiveLocaleCultureInfo)) + { + return Enumerable.Empty(); + } if (entityToken is AssociatedDataElementProviderHelperEntityToken associatedData) { From 65bd17f3e6cc266c4abb9ae7402f52e30872aab1 Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 17 Sep 2019 16:16:45 +0200 Subject: [PATCH 38/44] RenameUrlMappingNameForLocale fix - method deeply broken and was clearly never used. --- Composite/Core/Localization/LocalizationFacade.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/Core/Localization/LocalizationFacade.cs b/Composite/Core/Localization/LocalizationFacade.cs index 575bfa7229..f06c60ee2b 100644 --- a/Composite/Core/Localization/LocalizationFacade.cs +++ b/Composite/Core/Localization/LocalizationFacade.cs @@ -109,7 +109,7 @@ public static void RenameUrlMappingNameForLocale(string cultureName, string newU Verify.That(IsLocaleInstalled(cultureName), "The locale '{0}' is not installed and the url mapping name can not be renamed", cultureName); Verify.That(!IsUrlMappingNameInUse(cultureName, newUrlMappingName), "The url mapping '{0}' is already used", newUrlMappingName); - ISystemActiveLocale systemActiveLocale = DataFacade.GetData().Single(f => f.CultureName != cultureName); + ISystemActiveLocale systemActiveLocale = DataFacade.GetData().Single(f => f.CultureName == cultureName); systemActiveLocale.UrlMappingName = newUrlMappingName; DataFacade.Update(systemActiveLocale); } From 7b416109acf058e4c4d0011a4da22ba3baa0671e Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 18 Sep 2019 15:55:36 +0300 Subject: [PATCH 39/44] fix missed source code icon --- .../bindings/FunctionEditorPageBinding.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/Website/Composite/content/misc/editors/functioncalleditor/bindings/FunctionEditorPageBinding.js b/Website/Composite/content/misc/editors/functioncalleditor/bindings/FunctionEditorPageBinding.js index 2a4df5c486..38128ae299 100644 --- a/Website/Composite/content/misc/editors/functioncalleditor/bindings/FunctionEditorPageBinding.js +++ b/Website/Composite/content/misc/editors/functioncalleditor/bindings/FunctionEditorPageBinding.js @@ -61,17 +61,11 @@ FunctionEditorPageBinding.prototype.handleAction = function ( action ) { break; case PageBinding.ACTION_DOPOSTBACK : - if (action.target.getID() == "switchbutton") { - - //fix crashing Chrome if svg icon set, workarround - action.target.setImage(); - - if ( !this._isSourceMode ) { - this._cover ( false ); - var decks = this.bindingWindow.bindingMap.decks; - decks.select ( "sourcedeck" ); - this._isSourceMode = true; - } + if (action.target.getID() == "switchbutton" && !this._isSourceMode) { + this._cover ( false ); + var decks = this.bindingWindow.bindingMap.decks; + decks.select ( "sourcedeck" ); + this._isSourceMode = true; } break; From 70435b5f819308d711fe76704ad951ea19396cba Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 26 Sep 2019 16:13:32 +0200 Subject: [PATCH 40/44] C1 Console 'is another user editing this check' take data items language version into account. Fix #673 --- .../C1Console/Actions/ActionLockingFacade.cs | 78 ++++++++++++------- Composite/Data/Types/ILockingInformation.cs | 2 +- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/Composite/C1Console/Actions/ActionLockingFacade.cs b/Composite/C1Console/Actions/ActionLockingFacade.cs index 40beeda0a2..3c0c764d9d 100644 --- a/Composite/C1Console/Actions/ActionLockingFacade.cs +++ b/Composite/C1Console/Actions/ActionLockingFacade.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; @@ -21,7 +21,7 @@ namespace Composite.C1Console.Actions public static class ActionLockingFacade { private static readonly string LogTitle = typeof(ActionLockingFacade).Name; - private static Dictionary _lockingInformations = null; + private static Dictionary _lockingInformations = null; private static readonly object _lock = new object(); private static readonly IFormatter _ownerIdFormatter = new BinaryFormatter(); @@ -90,7 +90,8 @@ public static void ReleaseLock(EntityToken entityToken, object ownerId) { EnsureInitialization(); - RemoveLockingInformation(entityToken, ownerId); + string lockKey = GetLockKey(entityToken); + RemoveLockingInformation(lockKey, ownerId); } } @@ -108,14 +109,14 @@ public static void ReleaseAllLocks(object ownerId) { EnsureInitialization(); - List entityTokens = + List lockKeys = (from li in _lockingInformations where object.Equals(li.Value.OwnerId, ownerId) select li.Key).ToList(); - foreach (EntityToken entityToken in entityTokens) + foreach (string lockKey in lockKeys) { - RemoveLockingInformation(entityToken, ownerId); + RemoveLockingInformation(lockKey, ownerId); } } } @@ -132,8 +133,8 @@ public static bool IsLocked(EntityToken entityToken) using (GlobalInitializerFacade.CoreIsInitializedScope) { EnsureInitialization(); - - return _lockingInformations.ContainsKey(entityToken); + string lockKey = GetLockKey(entityToken); + return _lockingInformations.ContainsKey(lockKey); } } @@ -150,8 +151,10 @@ public static string LockedBy(EntityToken entityToken) { EnsureInitialization(); + string lockKey = GetLockKey(entityToken); + LockingInformation lockingInformation; - if (!_lockingInformations.TryGetValue(entityToken, out lockingInformation)) + if (!_lockingInformations.TryGetValue(lockKey, out lockingInformation)) { return null; } @@ -181,10 +184,10 @@ internal static void ReleaseAll(string username) { EnsureInitialization(); - List> itemsToRemove = + List> itemsToRemove = (from info in _lockingInformations where info.Value.Username == username - select new Tuple(info.Key, info.Value.OwnerId)).ToList(); + select new Tuple(info.Key, info.Value.OwnerId)).ToList(); foreach (var item in itemsToRemove) { @@ -205,9 +208,11 @@ public static void RemoveLock(EntityToken entityToken) { EnsureInitialization(); - if (_lockingInformations.ContainsKey(entityToken)) + string lockKey = GetLockKey(entityToken); + + if (_lockingInformations.ContainsKey(lockKey)) { - RemoveLockingInformation(entityToken, _lockingInformations[entityToken].OwnerId); + RemoveLockingInformation(lockKey, _lockingInformations[lockKey].OwnerId); } } } @@ -223,7 +228,7 @@ private static void DoInitialize() if (_lockingInformations == null) { - _lockingInformations = new Dictionary(); + _lockingInformations = new Dictionary(); LoadLockingInformation(); } @@ -251,9 +256,7 @@ private static void LoadLockingInformation() try { - EntityToken entityToken = EntityTokenSerializer.Deserialize(lockingInformation.SerializedEntityToken); - - _lockingInformations.Add(entityToken, li); + _lockingInformations.Add(lockingInformation.LockKey, li); } catch (Exception) { @@ -272,8 +275,10 @@ private static void LoadLockingInformation() private static void AddLockingInformation(EntityToken entityToken, object ownerId) { + string lockKey = GetLockKey(entityToken); + LockingInformation lockingInformation; - if (_lockingInformations.TryGetValue(entityToken, out lockingInformation)) + if (_lockingInformations.TryGetValue(lockKey, out lockingInformation)) { if (object.Equals(lockingInformation.OwnerId, ownerId)) { @@ -295,26 +300,26 @@ private static void AddLockingInformation(EntityToken entityToken, object ownerI ILockingInformation li = DataFacade.BuildNew(); li.Id = Guid.NewGuid(); - li.SerializedEntityToken = EntityTokenSerializer.Serialize(entityToken); + li.LockKey = lockKey; li.SerializedOwnerId = serializedOwnerId; li.Username = lockingInformation.Username; DataFacade.AddNew(li); - _lockingInformations.Add(entityToken, lockingInformation); + _lockingInformations.Add(lockKey, lockingInformation); } private static void UpdateLockingInformation(EntityToken entityToken, object newOwnerId) { - LockingInformation lockingInformation; - if (!_lockingInformations.TryGetValue(entityToken, out lockingInformation)) throw new InvalidOperationException("LockingInformation record missing"); + string lockKey = GetLockKey(entityToken); - string serializedEntityToken = EntityTokenSerializer.Serialize(entityToken); + LockingInformation lockingInformation; + if (!_lockingInformations.TryGetValue(lockKey, out lockingInformation)) throw new InvalidOperationException("LockingInformation record missing"); ILockingInformation lockingInformationDataItem = DataFacade.GetData(). - Single(f => f.SerializedEntityToken == serializedEntityToken); + Single(f => f.LockKey == lockKey); lockingInformationDataItem.SerializedOwnerId = SerializeOwnerId(newOwnerId); DataFacade.Update(lockingInformationDataItem); @@ -324,27 +329,26 @@ private static void UpdateLockingInformation(EntityToken entityToken, object new - private static void RemoveLockingInformation(EntityToken entityToken, object ownerId) + private static void RemoveLockingInformation(string lockKey, object ownerId) { LockingInformation lockingInformation; - if (!_lockingInformations.TryGetValue(entityToken, out lockingInformation)) return; + if (!_lockingInformations.TryGetValue(lockKey, out lockingInformation)) return; if (Equals(lockingInformation.OwnerId, ownerId)) { - _lockingInformations.Remove(entityToken); + _lockingInformations.Remove(lockKey); } string serializedOwnerId = SerializeOwnerId(ownerId); - string serializedEntityToken = EntityTokenSerializer.Serialize(entityToken); ILockingInformation lockingInformationDataItem = DataFacade.GetData() - .SingleOrDefault(f => f.SerializedEntityToken == serializedEntityToken + .SingleOrDefault(f => f.LockKey == lockKey && f.SerializedOwnerId == serializedOwnerId); if (lockingInformationDataItem == null) { - Log.LogWarning(LogTitle, "Failed to find entity token lock. EntityToken: " + serializedEntityToken); + Log.LogWarning(LogTitle, "Failed to find entity token lock. EntityToken: " + lockKey); return; } @@ -405,6 +409,20 @@ private static void Exit() } + private static string GetLockKey(EntityToken entityToken) + { + string lockKey = entityToken.Serialize(); + + if (entityToken is DataEntityToken) + { + var dataEntityToken = entityToken as DataEntityToken; + lockKey = lockKey + dataEntityToken.DataSourceId.LocaleScope.ToString(); + } + + return lockKey; + } + + private sealed class LockerToken : IDisposable diff --git a/Composite/Data/Types/ILockingInformation.cs b/Composite/Data/Types/ILockingInformation.cs index fcfa3d537f..ecb33c3c36 100644 --- a/Composite/Data/Types/ILockingInformation.cs +++ b/Composite/Data/Types/ILockingInformation.cs @@ -28,7 +28,7 @@ public interface ILockingInformation : IData [StoreFieldType(PhysicalStoreFieldType.LargeString)] [ImmutableFieldId("{34BD5C80-C5FD-4932-A1E6-3459E2D7802D}")] [NotNullValidator()] - string SerializedEntityToken { get; set; } + string LockKey { get; set; } /// From b4ec0c6abae7e92989a2e7c4541f11b2ffe53253 Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 27 Sep 2019 12:33:49 +0200 Subject: [PATCH 41/44] Adding null check, in case data with a null culture should ever come this way. --- Composite/C1Console/Actions/ActionLockingFacade.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/C1Console/Actions/ActionLockingFacade.cs b/Composite/C1Console/Actions/ActionLockingFacade.cs index 3c0c764d9d..829abbe10e 100644 --- a/Composite/C1Console/Actions/ActionLockingFacade.cs +++ b/Composite/C1Console/Actions/ActionLockingFacade.cs @@ -416,7 +416,7 @@ private static string GetLockKey(EntityToken entityToken) if (entityToken is DataEntityToken) { var dataEntityToken = entityToken as DataEntityToken; - lockKey = lockKey + dataEntityToken.DataSourceId.LocaleScope.ToString(); + lockKey = lockKey + (dataEntityToken.DataSourceId.LocaleScope ?? "").ToString(); } return lockKey; From a5026f06cbc4d6057319df367ba4d19149cf1bb4 Mon Sep 17 00:00:00 2001 From: mawtex Date: Mon, 30 Sep 2019 16:20:24 +0200 Subject: [PATCH 42/44] Improved null check that also compile. --- Composite/C1Console/Actions/ActionLockingFacade.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Composite/C1Console/Actions/ActionLockingFacade.cs b/Composite/C1Console/Actions/ActionLockingFacade.cs index 829abbe10e..f600799fae 100644 --- a/Composite/C1Console/Actions/ActionLockingFacade.cs +++ b/Composite/C1Console/Actions/ActionLockingFacade.cs @@ -416,7 +416,10 @@ private static string GetLockKey(EntityToken entityToken) if (entityToken is DataEntityToken) { var dataEntityToken = entityToken as DataEntityToken; - lockKey = lockKey + (dataEntityToken.DataSourceId.LocaleScope ?? "").ToString(); + if (dataEntityToken.DataSourceId != null && dataEntityToken.DataSourceId.LocaleScope != null) + { + lockKey = lockKey + dataEntityToken.DataSourceId.LocaleScope.ToString(); + } } return lockKey; From cff7467960c81f7ab538059041461124ad01cbec Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 24 Oct 2019 17:34:12 +0200 Subject: [PATCH 43/44] Expanding method info checks, so return value is within expectations, giving good log info if messed up. --- Composite/C1Console/Actions/FlowHandle.cs | 2 +- Composite/C1Console/Actions/FlowTokenSerializer.cs | 4 ++-- Composite/C1Console/Security/ActionTokenSerializer.cs | 4 ++-- Composite/C1Console/Security/EntityTokenSerializer.cs | 4 ++-- Composite/Core/Serialization/CompositeJsonSerializer.cs | 8 ++++++++ 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Composite/C1Console/Actions/FlowHandle.cs b/Composite/C1Console/Actions/FlowHandle.cs index 5cb7d76d3c..dd121ca26c 100644 --- a/Composite/C1Console/Actions/FlowHandle.cs +++ b/Composite/C1Console/Actions/FlowHandle.cs @@ -50,7 +50,7 @@ public static FlowHandle Deserialize(string serializedFlowHandle) Type flowTokenType = TypeManager.GetType(flowTokenTypeString); MethodInfo methodInfo = flowTokenType.GetMethod("Deserialize", BindingFlags.Public | BindingFlags.Static); - if (methodInfo == null) + if (methodInfo == null || !(typeof(FlowToken).IsAssignableFrom(methodInfo.ReturnType))) { throw new InvalidOperationException(string.Format("The flow token '{0}' is missing a public static Deserialize method taking a string as parameter and returning an '{1}'", flowTokenType, typeof(FlowToken))); } diff --git a/Composite/C1Console/Actions/FlowTokenSerializer.cs b/Composite/C1Console/Actions/FlowTokenSerializer.cs index 13b35d6705..5903281fee 100644 --- a/Composite/C1Console/Actions/FlowTokenSerializer.cs +++ b/Composite/C1Console/Actions/FlowTokenSerializer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Reflection; using System.Security; @@ -76,7 +76,7 @@ public static FlowToken Deserialize(string serialziedFlowToken, bool includeHash Type flowType = TypeManager.GetType(flowTokenTypeString); MethodInfo methodInfo = flowType.GetMethod("Deserialize", BindingFlags.Public | BindingFlags.Static); - if (methodInfo == null) + if (methodInfo == null || !(typeof(FlowToken).IsAssignableFrom(methodInfo.ReturnType))) { throw new InvalidOperationException(string.Format("The flow token {0} is missing a public static Deserialize method taking a string as parameter and returning an {1}", flowType, typeof(FlowToken))); } diff --git a/Composite/C1Console/Security/ActionTokenSerializer.cs b/Composite/C1Console/Security/ActionTokenSerializer.cs index 8fb04182eb..cd52bba4e2 100644 --- a/Composite/C1Console/Security/ActionTokenSerializer.cs +++ b/Composite/C1Console/Security/ActionTokenSerializer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Reflection; using System.Security; @@ -83,7 +83,7 @@ public static ActionToken Deserialize(string serialziedActionToken, bool include Type actionType = TypeManager.GetType(actionTokenTypeString); MethodInfo methodInfo = actionType.GetMethod("Deserialize", BindingFlags.Public | BindingFlags.Static); - if (methodInfo == null) + if (methodInfo == null || !(typeof(ActionToken).IsAssignableFrom(methodInfo.ReturnType))) { Log.LogWarning("ActionTokenSerializer", string.Format("The action token {0} is missing a public static Deserialize method taking a string as parameter and returning an {1}", actionType, typeof(ActionToken))); throw new InvalidOperationException(string.Format("The action token {0} is missing a public static Deserialize method taking a string as parameter and returning an {1}", actionType, typeof(ActionToken))); diff --git a/Composite/C1Console/Security/EntityTokenSerializer.cs b/Composite/C1Console/Security/EntityTokenSerializer.cs index a9de2f7666..86be9498a2 100644 --- a/Composite/C1Console/Security/EntityTokenSerializer.cs +++ b/Composite/C1Console/Security/EntityTokenSerializer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; using System.Security; using Composite.Core.Serialization; @@ -95,7 +95,7 @@ private static EntityToken DeserializeLegacy(string serializedEntityToken, bool Type entityType = TypeManager.GetType(entityTokenTypeString); MethodInfo methodInfo = entityType.GetMethod("Deserialize", BindingFlags.Public | BindingFlags.Static); - if (methodInfo == null) + if (methodInfo == null || !(typeof(EntityToken).IsAssignableFrom(methodInfo.ReturnType))) { throw new InvalidOperationException($"The entity token {entityType} is missing a public static Deserialize method taking a string as parameter and returning an {typeof(EntityToken)}"); } diff --git a/Composite/Core/Serialization/CompositeJsonSerializer.cs b/Composite/Core/Serialization/CompositeJsonSerializer.cs index cd37ee0abc..3a224715e8 100644 --- a/Composite/Core/Serialization/CompositeJsonSerializer.cs +++ b/Composite/Core/Serialization/CompositeJsonSerializer.cs @@ -228,6 +228,14 @@ public static T Deserialize(string str, bool isSigned) { return Deserialize(obj); } + + if (!(typeof(T).IsAssignableFrom(methodInfo.ReturnType))) + { + string typeName = str.GetValue(TypeKeyString); + Log.LogWarning("CompositeJsonSerializer", string.Format("The action {0} is missing a public static Deserialize method taking a string as parameter and returning an {1}", typeName, typeof(T))); + throw new InvalidOperationException(string.Format("The token {0} is missing a public static Deserialize method taking a string as parameter and returning an {1}", typeName, typeof(T))); + } + return (T)methodInfo.Invoke(null, new object[] { obj }); } From 97099e84072e968cccb99596f0cd3c435c94ee64 Mon Sep 17 00:00:00 2001 From: Anstsiya Date: Fri, 25 Oct 2019 13:12:29 +0300 Subject: [PATCH 44/44] Indexing media tags as a facet; showing raw value for incorrectly indexed date fields (#689) --- Composite/Composite.csproj | 1 + Composite/Data/Types/IMediaFile.cs | 4 +-- .../DateTimeDataFieldProcessor.cs | 12 ++++--- .../MediaTagsDataFieldProcessor.cs | 32 +++++++++++++++++++ .../DataTypeSearchReflectionHelper.cs | 8 ++++- 5 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 Composite/Search/Crawling/DataFieldProcessors/MediaTagsDataFieldProcessor.cs diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 0a4a811f99..58956dd4f5 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -265,6 +265,7 @@ + diff --git a/Composite/Data/Types/IMediaFile.cs b/Composite/Data/Types/IMediaFile.cs index 7615e9185f..0bb14bc5e0 100644 --- a/Composite/Data/Types/IMediaFile.cs +++ b/Composite/Data/Types/IMediaFile.cs @@ -1,4 +1,4 @@ -using System; +using System; using Composite.Data.Hierarchy; using Composite.Core.WebClient.Renderings.Data; @@ -58,7 +58,7 @@ public interface IMediaFile : IFile /// [ImmutableFieldId("{016372B5-9692-4C2D-B64D-8FC6594BBCFF}")] [StoreFieldType(PhysicalStoreFieldType.LargeString)] - [SearchableField(true, true, false)] + [SearchableField(true, true, true)] string Tags { get; set; } diff --git a/Composite/Search/Crawling/DataFieldProcessors/DateTimeDataFieldProcessor.cs b/Composite/Search/Crawling/DataFieldProcessors/DateTimeDataFieldProcessor.cs index 29b40e2735..c0496e9a78 100644 --- a/Composite/Search/Crawling/DataFieldProcessors/DateTimeDataFieldProcessor.cs +++ b/Composite/Search/Crawling/DataFieldProcessors/DateTimeDataFieldProcessor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Reflection; @@ -21,10 +21,12 @@ protected override DocumentFieldPreview.ValuePreviewDelegate GetPreviewFunction( return value => { if (value == null) return null; - var date = DateTime.ParseExact((string)value, "s", CultureInfo.InvariantCulture); - - return date.ToString("yyyy MMM d"); - }; + if (DateTime.TryParseExact((string)value, "s", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) + { + return date.ToString("yyyy MMM d"); + } + return (string)value; + }; } /// diff --git a/Composite/Search/Crawling/DataFieldProcessors/MediaTagsDataFieldProcessor.cs b/Composite/Search/Crawling/DataFieldProcessors/MediaTagsDataFieldProcessor.cs new file mode 100644 index 0000000000..8527074194 --- /dev/null +++ b/Composite/Search/Crawling/DataFieldProcessors/MediaTagsDataFieldProcessor.cs @@ -0,0 +1,32 @@ +using Composite.Core.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Composite.Search.Crawling.DataFieldProcessors +{ + class MediaTagsDataFieldProcessor : DefaultDataFieldProcessor + { + public override string[] GetFacetValues(object value) + { + var str = (string)value; + var strList = str.Split(',').Select(s => s.Trim()).Where(s => !s.IsNullOrEmpty()).ToArray(); + return strList; + } + + public override DocumentFieldFacet GetDocumentFieldFacet(PropertyInfo propertyInfo) + { + var result = base.GetDocumentFieldFacet(propertyInfo); + + result.FacetType = FacetType.MultipleValues; + + return result; + } + + } + +} + diff --git a/Composite/Search/Crawling/DataTypeSearchReflectionHelper.cs b/Composite/Search/Crawling/DataTypeSearchReflectionHelper.cs index c876057c11..839f61e3c4 100644 --- a/Composite/Search/Crawling/DataTypeSearchReflectionHelper.cs +++ b/Composite/Search/Crawling/DataTypeSearchReflectionHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -81,6 +81,12 @@ internal static IDataFieldProcessor GetDataFieldProcessor(PropertyInfo propertyI return new MimeTypeDataFieldProcessor(); } + if (propertyInfo.DeclaringType == typeof(IMediaFile) + && propertyInfo.Name == nameof(IMediaFile.Tags)) + { + return new MediaTagsDataFieldProcessor(); + } + return new DefaultDataFieldProcessor(); }); }