From 53744fac4b73f1655e44eb17194292bcfbfa6fba Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 2 Jul 2018 17:31:07 +0200 Subject: [PATCH 01/51] Logging user name when installing/uninstalling a package --- .../Core/PackageSystem/PackageManagerInstallProcess.cs | 7 +++++-- Composite/Core/PackageSystem/PackageUninstaller.cs | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Composite/Core/PackageSystem/PackageManagerInstallProcess.cs b/Composite/Core/PackageSystem/PackageManagerInstallProcess.cs index 400e293f92..1fa4c01920 100644 --- a/Composite/Core/PackageSystem/PackageManagerInstallProcess.cs +++ b/Composite/Core/PackageSystem/PackageManagerInstallProcess.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -8,6 +8,7 @@ using System.ComponentModel; using System.Text; using System.Xml.Linq; +using Composite.C1Console.Security; using Composite.Core.Application; @@ -223,7 +224,9 @@ public List Install() if (_validationResult.Count > 0) throw new InvalidOperationException("Installation did not validate"); Verify.IsNull(_installationResult, "Install may only be called once"); - Log.LogInformation(LogTitle, "Installing package: {0}, Version: {1}, Id = {2}", _packageName, _packageVersion, _packageId); + var userName = UserValidationFacade.IsLoggedIn() ? UserValidationFacade.GetUsername() : ""; + + Log.LogInformation(LogTitle, $"Installing package: {_packageName}, Version: {_packageVersion}, Id = {_packageId}; User name: '{userName}'"); PackageFragmentValidationResult result = _packageInstaller.Install(_systemLockingType); diff --git a/Composite/Core/PackageSystem/PackageUninstaller.cs b/Composite/Core/PackageSystem/PackageUninstaller.cs index e59316f7cd..9b965f012c 100644 --- a/Composite/Core/PackageSystem/PackageUninstaller.cs +++ b/Composite/Core/PackageSystem/PackageUninstaller.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; +using Composite.C1Console.Security; using Composite.Core.Application; using Composite.Core.IO; using Composite.Core.IO.Zip; @@ -139,8 +140,8 @@ private void DoUninstall() private void DoUninstallWithoutTransaction() { - Log.LogInformation(LogTitle, "Uninstalling package '{0}', Id = {1}", PackageInformation.Name, PackageInformation.Id); - + var userName = UserValidationFacade.IsLoggedIn() ? UserValidationFacade.GetUsername() : ""; + Log.LogInformation(LogTitle, $"Uninstalling package '{PackageInformation.Name}', Id = {PackageInformation.Id}. User name: '{userName}'"); try { From bbcf9871d56fbfba5cadcde9aa2acca4ab813cd9 Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Tue, 3 Jul 2018 15:39:37 +0200 Subject: [PATCH 02/51] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7634b0528b..4e2b8d7695 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,6 @@ Download binaries from https://github.com/Orckestra/CMS-Foundation/releases/late Head over to https://gitter.im/Orckestra/C1-CMS or add an issue ## Who are we? ## -Orckestra is the company driving the development of C1 CMS Foundation. We have a team working full time on this CMS and on other cool stuff you can add to it. We are situated in Austin, Montreal, Copenhagen and Kiev. We specialize in enterprise commerce software. +Orckestra is the company driving the development of C1 CMS Foundation. We have a team working full time on this CMS and on other cool stuff you can add to it. We are situated in Montreal, Copenhagen and Kiev. We specialize in enterprise omni channel commerce software. You can visit us at http://c1.orckestra.com and http://www.orckestra.com/ From add1e2549c18b20b951b174dffdb9647cc179f8e Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 16 Jul 2018 17:15:32 +0200 Subject: [PATCH 03/51] Fixing console showing YSOD if there's a problem with DNS on the host machine --- Composite/Core/WebClient/ScriptLoader.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Composite/Core/WebClient/ScriptLoader.cs b/Composite/Core/WebClient/ScriptLoader.cs index 9d9580ca2a..1f5d3edb58 100644 --- a/Composite/Core/WebClient/ScriptLoader.cs +++ b/Composite/Core/WebClient/ScriptLoader.cs @@ -1,10 +1,12 @@ -using System; +using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Net; +using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using System.Web; @@ -249,7 +251,16 @@ private bool UrlIsOnPublicNet(Uri currentUri) if (hostname.IndexOf('.') == -1) return false; - var dnsResult = System.Net.Dns.GetHostEntry(hostname); + IPHostEntry dnsResult; + try + { + dnsResult = System.Net.Dns.GetHostEntry(hostname); + } + catch (SocketException) + { + return false; + } + if (dnsResult.AddressList.Length == 0) { return false; From b70398e44a3e5c11afc787a2ad8e366e86dd6b0b Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 17 Jul 2018 12:00:51 +0200 Subject: [PATCH 04/51] Small fix in PageMetaDataFacade --- Composite/Data/PageMetaDataFacade.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Composite/Data/PageMetaDataFacade.cs b/Composite/Data/PageMetaDataFacade.cs index b15e551e4d..1c0bf5d872 100644 --- a/Composite/Data/PageMetaDataFacade.cs +++ b/Composite/Data/PageMetaDataFacade.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -229,9 +229,9 @@ public static List GetAllowedMetaDataDefinitions(this I /// public static bool IsDefinitionAllowed(IPageMetaDataDefinition pageMetaDataDefinition, IPage page) { - Guid pageId = page.GetPageIdOrNull(); + if (page != null && pageMetaDataDefinition.DefiningItemId == page.PageTypeId) return true; - if (pageMetaDataDefinition.DefiningItemId == page.PageTypeId) return true; + Guid pageId = page.GetPageIdOrNull(); // Its not a pagetype attached meta data definitions, check page attacked int levelsToParent = CountLevelsToParent(pageMetaDataDefinition.DefiningItemId, pageId); @@ -297,6 +297,8 @@ public static IEnumerable GetMetaData(string definitionName, Type metaDat /// public static IEnumerable GetMetaData(this IPage page) { + Verify.ArgumentNotNull(page, nameof(page)); + return GetMetaData(page, DataScopeManager.CurrentDataScope); } @@ -305,6 +307,8 @@ public static IEnumerable GetMetaData(this IPage page) /// public static IEnumerable GetMetaData(this IPage page, DataScopeIdentifier dataScopeIdentifier) { + Verify.ArgumentNotNull(page, nameof(page)); + using (new DataScope(dataScopeIdentifier)) { foreach (IPageMetaDataDefinition pageMetaDataDefinition in page.GetAllowedMetaDataDefinitions()) From c63db9f18d9390bfc1ad5559599e4a7feba18519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pauli=20=C3=98ster=C3=B8?= Date: Tue, 31 Jul 2018 18:45:27 -0200 Subject: [PATCH 05/51] Initial commit of a new SiteMap plugin infrastructure - This new plugin system enables 3rd party packages like blog to easily provide SiteMap nodes at runtime which will be automatically reflected in both menus and sitemap.xml. - Plugins are registered as services during startup - Even normal pages is exposed via a plugin for easy access to solutions to replace the plugin if needed. No more writing new SiteMapProviders and SiteMapHandlers from scratch to inject custom nodes and if one should find the need, common properties and methods on nodes and provider is exposed via interfaces so we're no longer tied up to concrete class types. This aims to fullfill the needs in issue 456 --- Composite/AspNet/CmsPageSiteMapNode.cs | 59 ++--- Composite/AspNet/CmsPageSiteMapProvider.cs | 220 ++++++++++-------- Composite/AspNet/CmsPagesSiteMapPlugin.cs | 107 +++++++++ Composite/AspNet/ICmsSiteMapNode.cs | 18 ++ Composite/AspNet/ICmsSiteMapProvider.cs | 11 + Composite/AspNet/ISchemaOrgSiteMapNode.cs | 34 +++ Composite/AspNet/ISiteMapPlugin.cs | 57 +++++ Composite/AspNet/SiteMapHandler.cs | 18 +- Composite/Composite.csproj | 5 + .../ApplicationLevelEventHandlers.cs | 1 + 10 files changed, 385 insertions(+), 145 deletions(-) create mode 100644 Composite/AspNet/CmsPagesSiteMapPlugin.cs create mode 100644 Composite/AspNet/ICmsSiteMapNode.cs create mode 100644 Composite/AspNet/ICmsSiteMapProvider.cs create mode 100644 Composite/AspNet/ISchemaOrgSiteMapNode.cs create mode 100644 Composite/AspNet/ISiteMapPlugin.cs diff --git a/Composite/AspNet/CmsPageSiteMapNode.cs b/Composite/AspNet/CmsPageSiteMapNode.cs index 6352b1b657..ed7f1716f5 100644 --- a/Composite/AspNet/CmsPageSiteMapNode.cs +++ b/Composite/AspNet/CmsPageSiteMapNode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Web; using Composite.Core.Routing; @@ -10,30 +10,20 @@ namespace Composite.AspNet /// /// Represents an instance in a sitemap. /// - public class CmsPageSiteMapNode: SiteMapNode + public class CmsPageSiteMapNode : SiteMapNode, ICmsSiteMapNode, ISchemaOrgSiteMapNode { private int? _depth; - /// - /// Gets or sets the culture. - /// - /// - /// The culture. - /// - public CultureInfo Culture { get; protected set; } + /// + public CultureInfo Culture { get; } - /// - /// Gets or sets the priority. - /// - /// - /// The priority. - /// + /// public int? Priority { get; protected set; } /// /// Gets the current page. /// - public IPage Page { get; protected set; } + public IPage Page { get; } /// /// Gets or sets the depth. @@ -47,10 +37,11 @@ public int Depth { if (_depth == null) { - int depth = 0; - Guid id = Page.Id; + var depth = 0; + var id = Page.Id; const int maxDepth = 1000; + using (new DataScope(Page.DataSourceId.PublicationScope, Page.DataSourceId.LocaleScope)) { while (id != Guid.Empty && depth < maxDepth) @@ -58,6 +49,7 @@ public int Depth depth++; id = PageManager.GetParentId(id); } + if (depth == maxDepth) { throw new InvalidOperationException("Endless page loop"); @@ -66,25 +58,17 @@ public int Depth _depth = depth; } + return _depth.Value; } - protected set { _depth = value; } + + protected set => _depth = value; } - /// - /// Gets or sets the last modified. - /// - /// - /// The last modified. - /// - public DateTime LastModified { get; protected set; } + /// + public DateTime LastModified { get; } - /// - /// Gets or sets the change frequency. - /// - /// - /// The change frequency. - /// + /// public SiteMapNodeChangeFrequency? ChangeFrequency { get; protected set; } /// @@ -93,7 +77,7 @@ public int Depth /// /// The document title. /// - public string DocumentTitle { get; protected set; } + public string DocumentTitle { get; } /// /// Initializes a new instance of the class. @@ -111,14 +95,13 @@ public CmsPageSiteMapNode(SiteMapProvider provider, IPage page) Culture = page.DataSourceId.LocaleScope; } - /// public bool Equals(CmsPageSiteMapNode obj) { return Key == obj.Key && Culture.Equals(obj.Culture); } - /// + /// public override bool Equals(object obj) { var pageSiteMapNode = obj as CmsPageSiteMapNode; @@ -126,17 +109,17 @@ public override bool Equals(object obj) { return Equals(pageSiteMapNode); } - + return base.Equals(obj); } - /// + /// public override SiteMapNode Clone() { return new CmsPageSiteMapNode(this.Provider, Page); } - /// + /// public override int GetHashCode() { return Key.GetHashCode() ^ Culture.GetHashCode(); diff --git a/Composite/AspNet/CmsPageSiteMapProvider.cs b/Composite/AspNet/CmsPageSiteMapProvider.cs index f83a8bcfbd..966fd22f41 100644 --- a/Composite/AspNet/CmsPageSiteMapProvider.cs +++ b/Composite/AspNet/CmsPageSiteMapProvider.cs @@ -1,100 +1,91 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Web; -using Composite.Core.Extensions; -using Composite.Core.Routing; -using Composite.Core.WebClient.Renderings.Page; +using Composite.Core; using Composite.Data; -using Composite.Data.Types; namespace Composite.AspNet { /// - /// Implementation of that returns cms pages + /// Implementation of which returns nodes returned by registered . /// - public class CmsPageSiteMapProvider : SiteMapProvider + public class CmsPageSiteMapProvider : SiteMapProvider, ICmsSiteMapProvider { - private static readonly SiteMapNodeCollection EmptyCollection = - SiteMapNodeCollection.ReadOnly(new SiteMapNodeCollection()); + private static readonly SiteMapNodeCollection EmptyCollection = SiteMapNodeCollection.ReadOnly(new SiteMapNodeCollection()); - /// - public override SiteMapProvider ParentProvider { get; set; } + private readonly List _plugins; - /// + /// public override SiteMapNode CurrentNode { get { var context = HttpContext.Current; - var node = ResolveSiteMapNode(context) ?? FindSiteMapNode(context); - return ReturnNodeIfAccessible(node); + return SecurityTrimNode(node); } } /// - public override SiteMapNode RootNode => ReturnNodeIfAccessible(GetRootNodeCore()); + public override SiteMapProvider RootProvider => ParentProvider?.RootProvider ?? this; + /// + public override SiteMapNode RootNode => SecurityTrimNode(GetRootNodeCore()); - private SiteMapNode ReturnNodeIfAccessible(SiteMapNode node) + /// + public CmsPageSiteMapProvider() { - if (node != null && node.IsAccessibleToUser(HttpContext.Current)) - { - return node; - } - return null; + _plugins = ServiceLocator.GetServices().ToList(); } - - - - /// - public override SiteMapProvider RootProvider + /// + public override SiteMapNode FindSiteMapNode(HttpContext context) { - get + var contextBase = new HttpContextWrapper(context); + + foreach (var plugin in _plugins) { - return ParentProvider?.RootProvider ?? this; + var node = plugin.FindSiteMapNode(this, contextBase); + if (node != null) + { + return SecurityTrimNode(node); + } } - } - /// - public override SiteMapNode FindSiteMapNode(HttpContext context) - { - string key = PageRenderer.CurrentPageId.ToString(); - - return FindSiteMapNodeFromKey(key); + return null; } - - /// + /// public override SiteMapNode FindSiteMapNodeFromKey(string key) { - Guid pageId = new Guid(key); - var page = PageManager.GetPageById(pageId); + foreach (var plugin in _plugins) + { + var node = plugin.FindSiteMapNodeFromKey(this, key); + if (node != null) + { + return SecurityTrimNode(node); + } + } - return page != null ? new CmsPageSiteMapNode(this, page) : null; + return null; } - /// + /// public override SiteMapNodeCollection GetChildNodes(SiteMapNode node) { - Verify.ArgumentNotNull(node, "node"); - - var pageSiteMapNode = (CmsPageSiteMapNode)node; + Verify.ArgumentNotNull(node, nameof(node)); - var context = HttpContext.Current; + var childNodes = new List(); - List childNodes; - using (new DataScope(pageSiteMapNode.Culture)) + foreach (var plugin in _plugins) { - childNodes = PageManager.GetChildrenIDs(pageSiteMapNode.Page.Id) - .Select(PageManager.GetPageById) - .Where(p => p != null) - .Select(p => new CmsPageSiteMapNode(this, p)) - .OfType() - .ToList(); + var pluginChildNodes = plugin.GetChildNodes(node); + if (pluginChildNodes != null) + { + childNodes.AddRange(pluginChildNodes); + } } if (!childNodes.Any()) @@ -102,54 +93,39 @@ public override SiteMapNodeCollection GetChildNodes(SiteMapNode node) return EmptyCollection; } - if (SecurityTrimmingEnabled) - { - childNodes = childNodes.Where(child => child.IsAccessibleToUser(context)).ToList(); - } + childNodes = SecurityTrimList(childNodes); return new SiteMapNodeCollection(childNodes.ToArray()); } - /// + /// public override SiteMapNode GetParentNode(SiteMapNode node) { - Verify.ArgumentNotNull(node, "node"); + Verify.ArgumentNotNull(node, nameof(node)); - var pageSiteMapNode = (CmsPageSiteMapNode)node; + SiteMapNode parentNode = null; - IPage parentPage = null; - using (new DataScope(pageSiteMapNode.Culture)) + foreach (var plugin in _plugins) { - Guid parentPageId = PageManager.GetParentId(pageSiteMapNode.Page.Id); - if (parentPageId != Guid.Empty) + parentNode = plugin.GetParentNode(node); + if (parentNode != null) { - parentPage = PageManager.GetPageById(parentPageId); + break; } } - SiteMapNode parentNode; - if (parentPage != null) - { - parentNode = new CmsPageSiteMapNode(this, parentPage); - } - else - { - parentNode = ParentProvider?.GetParentNode(node); - } - - return parentNode != null && parentNode.IsAccessibleToUser(HttpContext.Current) ? parentNode : null; + return SecurityTrimNode(parentNode); } - /// + /// protected override SiteMapNode GetRootNodeCore() { - var context = SiteMapContext.Current; - - var rootPage = context?.RootPage; + var siteMapContext = SiteMapContext.Current; + var rootPage = siteMapContext?.RootPage; if (rootPage == null) { - Guid homePageId = SitemapNavigator.CurrentHomePageId; + var homePageId = SitemapNavigator.CurrentHomePageId; if (homePageId == Guid.Empty) { homePageId = PageManager.GetChildrenIDs(Guid.Empty).FirstOrDefault(); @@ -168,52 +144,98 @@ protected override SiteMapNode GetRootNodeCore() var node = new CmsPageSiteMapNode(this, rootPage); - return node.IsAccessibleToUser(HttpContext.Current) ? node : null; + return SecurityTrimNode(node); } /// public ICollection GetRootNodes() { var list = new List(); - foreach (Guid rootPageId in PageManager.GetChildrenIDs(Guid.Empty)) + + foreach (var rootPageId in PageManager.GetChildrenIDs(Guid.Empty)) { foreach (var culture in DataLocalizationFacade.ActiveLocalizationCultures) - using (new DataScope(culture)) { - var page = PageManager.GetPageById(rootPageId); - if (page != null) + using (new DataScope(culture)) { - list.Add(new CmsPageSiteMapNode(this, page)); + var page = PageManager.GetPageById(rootPageId); + if (page != null) + { + list.Add(new CmsPageSiteMapNode(this, page)); + } } } } - return list; + return SecurityTrimList(list); } - - /// + /// public override SiteMapNode FindSiteMapNode(string rawUrl) { - var pageUrl = PageUrls.ParseUrl(rawUrl); - if (pageUrl == null || !string.IsNullOrEmpty(pageUrl.PathInfo)) + foreach (var plugin in _plugins) { - return null; + var node = plugin.FindSiteMapNode(this, rawUrl); + if (node != null) + { + return SecurityTrimNode(node); + } + } + + return null; + } + + /// + public override bool IsAccessibleToUser(HttpContext ctx, SiteMapNode node) + { + var ctxBase = new HttpContextWrapper(ctx); + + foreach (var plugin in _plugins) + { + var isAccessibleToUser = plugin.IsAccessibleToUser(ctxBase, node); + if (!isAccessibleToUser) + { + return false; + } } - var page = pageUrl.GetPage(); - if (page == null) + return true; + } + + private T SecurityTrimNode(T node) where T : SiteMapNode + { + if (node == null) { return null; } - return new CmsPageSiteMapNode(this, page); + if (SecurityTrimmingEnabled) + { + var context = HttpContext.Current; + if (!node.IsAccessibleToUser(context)) + { + return null; + } + } + + return node; } - /// - public override bool IsAccessibleToUser(HttpContext ctx, SiteMapNode node) + private List SecurityTrimList(List list) where T : SiteMapNode { - return true; + if (list == null) + { + return null; + } + + if (SecurityTrimmingEnabled) + { + var context = HttpContext.Current; + + return list.Where(child => child.IsAccessibleToUser(context)).ToList(); + } + + return list; } } } diff --git a/Composite/AspNet/CmsPagesSiteMapPlugin.cs b/Composite/AspNet/CmsPagesSiteMapPlugin.cs new file mode 100644 index 0000000000..23df655360 --- /dev/null +++ b/Composite/AspNet/CmsPagesSiteMapPlugin.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Composite.Core.Extensions; +using Composite.Core.Routing; +using Composite.Core.WebClient.Renderings.Page; +using Composite.Data; +using Composite.Data.Types; + +namespace Composite.AspNet +{ + /// + public class CmsPagesSiteMapPlugin : ISiteMapPlugin + { + /// + public List GetChildNodes(SiteMapNode node) + { + var pageSiteMapNode = node as CmsPageSiteMapNode; + if (pageSiteMapNode != null) + { + using (new DataScope(pageSiteMapNode.Culture)) + { + var pageChildNodes = PageManager.GetChildrenIDs(pageSiteMapNode.Page.Id) + .Select(PageManager.GetPageById) + .Where(p => p != null) + .Select(p => new CmsPageSiteMapNode(node.Provider, p)) + .OfType() + .ToList(); + + return pageChildNodes; + } + } + + return null; + } + + /// + public SiteMapNode GetParentNode(SiteMapNode node) + { + var pageSiteMapNode = node as CmsPageSiteMapNode; + if (pageSiteMapNode != null) + { + IPage parentPage = null; + + using (new DataScope(pageSiteMapNode.Culture)) + { + var parentPageId = PageManager.GetParentId(pageSiteMapNode.Page.Id); + if (parentPageId != Guid.Empty) + { + parentPage = PageManager.GetPageById(parentPageId); + } + } + + if (parentPage != null) + { + return new CmsPageSiteMapNode(node.Provider, parentPage); + } + + return node.Provider.ParentProvider?.GetParentNode(node); + } + + return null; + } + + /// + public SiteMapNode FindSiteMapNode(SiteMapProvider provider, HttpContextBase context) + { + var key = PageRenderer.CurrentPageId.ToString(); + + return FindSiteMapNodeFromKey(provider, key); + } + + /// + public SiteMapNode FindSiteMapNode(SiteMapProvider provider, string rawUrl) + { + var pageUrl = PageUrls.ParseUrl(rawUrl); + if (pageUrl == null || !String.IsNullOrEmpty(pageUrl.PathInfo)) + { + return null; + } + + var page = pageUrl.GetPage(); + if (page == null) + { + return null; + } + + return new CmsPageSiteMapNode(provider, page); + } + + /// + public SiteMapNode FindSiteMapNodeFromKey(SiteMapProvider provider, string key) + { + var pageId = new Guid(key); + var page = PageManager.GetPageById(pageId); + + return page != null ? new CmsPageSiteMapNode(provider, page) : null; + } + + /// + public bool IsAccessibleToUser(HttpContextBase context, SiteMapNode node) + { + return true; + } + } +} \ No newline at end of file diff --git a/Composite/AspNet/ICmsSiteMapNode.cs b/Composite/AspNet/ICmsSiteMapNode.cs new file mode 100644 index 0000000000..f2ad6a1bc1 --- /dev/null +++ b/Composite/AspNet/ICmsSiteMapNode.cs @@ -0,0 +1,18 @@ +using System.Globalization; + +namespace Composite.AspNet +{ + /// + /// + /// + public interface ICmsSiteMapNode + { + /// + /// Gets or sets the culture. + /// + /// + /// The culture. + /// + CultureInfo Culture { get; } + } +} diff --git a/Composite/AspNet/ICmsSiteMapProvider.cs b/Composite/AspNet/ICmsSiteMapProvider.cs new file mode 100644 index 0000000000..4597d1965e --- /dev/null +++ b/Composite/AspNet/ICmsSiteMapProvider.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Composite.AspNet +{ + /// + public interface ICmsSiteMapProvider + { + /// + ICollection GetRootNodes(); + } +} diff --git a/Composite/AspNet/ISchemaOrgSiteMapNode.cs b/Composite/AspNet/ISchemaOrgSiteMapNode.cs new file mode 100644 index 0000000000..935c54e842 --- /dev/null +++ b/Composite/AspNet/ISchemaOrgSiteMapNode.cs @@ -0,0 +1,34 @@ +using System; + +namespace Composite.AspNet +{ + /// + /// + /// + public interface ISchemaOrgSiteMapNode + { + /// + /// Gets or sets the last modified. + /// + /// + /// The last modified. + /// + DateTime LastModified { get; } + + /// + /// Gets or sets the change frequency. + /// + /// + /// The change frequency. + /// + SiteMapNodeChangeFrequency? ChangeFrequency { get; } + + /// + /// Gets or sets the priority. + /// + /// + /// The priority. + /// + int? Priority { get; } + } +} diff --git a/Composite/AspNet/ISiteMapPlugin.cs b/Composite/AspNet/ISiteMapPlugin.cs new file mode 100644 index 0000000000..273c77121d --- /dev/null +++ b/Composite/AspNet/ISiteMapPlugin.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Web; + +namespace Composite.AspNet +{ + /// + /// Defines the contract for a plugin to interact with to provide and handle security trimming + /// + public interface ISiteMapPlugin + { + /// + /// + /// + /// + /// + List GetChildNodes(SiteMapNode node); + + /// + /// + /// + /// + /// + SiteMapNode GetParentNode(SiteMapNode node); + + /// + /// + /// + /// + /// + /// + SiteMapNode FindSiteMapNode(SiteMapProvider provider, string rawUrl); + + /// + /// + /// + /// + /// + /// + SiteMapNode FindSiteMapNode(SiteMapProvider provider, HttpContextBase context); + + /// + /// + /// + /// + /// + /// + SiteMapNode FindSiteMapNodeFromKey(SiteMapProvider provider, string key); + + /// + /// + /// + /// + /// + /// + bool IsAccessibleToUser(HttpContextBase context, SiteMapNode node); + } +} diff --git a/Composite/AspNet/SiteMapHandler.cs b/Composite/AspNet/SiteMapHandler.cs index 5ec6a1cdd1..435fd4b55d 100644 --- a/Composite/AspNet/SiteMapHandler.cs +++ b/Composite/AspNet/SiteMapHandler.cs @@ -33,6 +33,10 @@ void IHttpHandler.ProcessRequest(HttpContext context) context.Response.ContentEncoding = Encoding.UTF8; var provider = SiteMap.Provider; + if (!(provider is ICmsSiteMapProvider)) + { + throw new NotSupportedException("Configured sitemap provider is not supported"); + } var writer = XmlWriter.Create(context.Response.OutputStream, new XmlWriterSettings {Encoding = Encoding.UTF8, Indent = true}); @@ -41,8 +45,7 @@ void IHttpHandler.ProcessRequest(HttpContext context) if (IsRootRequest(context.Request.RawUrl)) { - var rootNodes = ((CmsPageSiteMapProvider) provider).GetRootNodes(); - + var rootNodes = ((ICmsSiteMapProvider)provider).GetRootNodes(); if (rootNodes.Count > 1) { WriteSiteMapList(writer, rootNodes); @@ -257,15 +260,14 @@ private void WriteElement(XmlWriter writer, SiteMapNode node, HashSet al writer.WriteString($"{_requestUrl.Scheme}://{_requestUrl.Host}{node.Url}"); writer.WriteEndElement(); - var baseNode = node as CmsPageSiteMapNode; - if (baseNode != null) + if (node is ISchemaOrgSiteMapNode schemaOrgSiteMapNode) { - var lastEdited = baseNode.LastModified; + var lastEdited = schemaOrgSiteMapNode.LastModified; writer.WriteStartElement("lastmod"); writer.WriteString(lastEdited.ToUniversalTime().ToString("u").Replace(" ", "T")); writer.WriteEndElement(); - var changeFrequency = baseNode.ChangeFrequency; + var changeFrequency = schemaOrgSiteMapNode.ChangeFrequency; if (changeFrequency.HasValue) { writer.WriteStartElement("changefreq"); @@ -273,13 +275,13 @@ private void WriteElement(XmlWriter writer, SiteMapNode node, HashSet al writer.WriteEndElement(); } - var priority = baseNode.Priority; + var priority = schemaOrgSiteMapNode.Priority; if (priority.HasValue) { if (priority > 1 && priority < 10) { writer.WriteStartElement("priority"); - writer.WriteString(((decimal) priority.Value/10).ToString("0.0", CultureInfo.InvariantCulture)); + writer.WriteString(((decimal)priority.Value / 10).ToString("0.0", CultureInfo.InvariantCulture)); writer.WriteEndElement(); } } diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 8b5cf4e758..980f8a9175 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -211,7 +211,12 @@ + + + + + diff --git a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs index 57abdaef53..fe001a1618 100644 --- a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs +++ b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs @@ -109,6 +109,7 @@ private static void InitializeServices() services.AddSingleton(new SmtpMailer()); + services.AddTransient(); VersionedDataHelper.Initialize(); } From 21ee93eaac1423ce013cfa50ccb879582bfe0587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pauli=20=C3=98ster=C3=B8?= Date: Mon, 13 Aug 2018 14:58:05 -0200 Subject: [PATCH 06/51] Fix SiteMapProvider doesn't resolve rootnode correctly for non-page requests When calling RootNode on the SiteMapProvider from outside a CMS Page Request, that is, there is no CurrentPage, the provider incorrectly returns the first website regardless of what hostname is being used. This fix takes the hostname of the current request into account, when present, to resovle the correct Root node on multisite installations. Fixes https://github.com/Orckestra/C1-CMS-Foundation/issues/610 --- Composite/AspNet/CmsPageSiteMapProvider.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Composite/AspNet/CmsPageSiteMapProvider.cs b/Composite/AspNet/CmsPageSiteMapProvider.cs index f83a8bcfbd..8f86690910 100644 --- a/Composite/AspNet/CmsPageSiteMapProvider.cs +++ b/Composite/AspNet/CmsPageSiteMapProvider.cs @@ -152,7 +152,20 @@ protected override SiteMapNode GetRootNodeCore() Guid homePageId = SitemapNavigator.CurrentHomePageId; if (homePageId == Guid.Empty) { - homePageId = PageManager.GetChildrenIDs(Guid.Empty).FirstOrDefault(); + var context = HttpContext.Current; + if (context == null) + { + homePageId = PageManager.GetChildrenIDs(Guid.Empty).FirstOrDefault(); + } + else + { + using (var data = new DataConnection()) + { + var pageNode = data.SitemapNavigator.GetPageNodeByHostname(context.Request.Url.Host); + + homePageId = pageNode.Id; + } + } } if (homePageId != Guid.Empty) From 74c5f22743030295e913d2c5a614cd7c3cc4d39b Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 14 Aug 2018 14:33:27 +0200 Subject: [PATCH 07/51] Tree Definition XML now support attribute "BrowserUrl" for and , ex . For DataElements you can reference data item values using the same $C1:Data:(type name):(field name)} syntax used for the Label attribute. --- .../C1Console/Trees/DataElementsTreeNode.cs | 18 +++++++++++++++++- .../Foundation/TreeNodeCreatorFactory.cs | 14 +++++++++----- .../C1Console/Trees/SimpleElementTreeNode.cs | 10 +++++++++- Website/Composite/schemas/Trees/Tree.xsd | 19 ++++++++++++++++--- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/Composite/C1Console/Trees/DataElementsTreeNode.cs b/Composite/C1Console/Trees/DataElementsTreeNode.cs index 54b689e032..27159de61d 100644 --- a/Composite/C1Console/Trees/DataElementsTreeNode.cs +++ b/Composite/C1Console/Trees/DataElementsTreeNode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -47,6 +47,9 @@ public class DataElementsTreeNode : DataFilteringTreeNode /// public bool ShowForeignItems { get; internal set; } // Optional + /// + public string BrowserUrl { get; internal set; } // Optional + // Cached values private Dictionary ParentFilteringHelpers { get; set; } @@ -61,6 +64,7 @@ public class DataElementsTreeNode : DataFilteringTreeNode private DynamicValuesHelper LabelDynamicValuesHelper { get; set; } private DynamicValuesHelper ToolTipDynamicValuesHelper { get; set; } + private DynamicValuesHelper BrowserUrlDynamicValuesHelper { get; set; } private static readonly ResourceHandle LocalizeDataTypeIcon = ResourceHandle.BuildIconFromDefaultProvider("tree-localize-data"); @@ -353,6 +357,12 @@ EntityToken parentEntityToken } } + if (this.BrowserUrl != null) + { + element.PropertyBag.Add("BrowserUrl", this.BrowserUrlDynamicValuesHelper.ReplaceValues(replaceContext)); + element.PropertyBag.Add("BrowserToolingOn", "false"); + } + if (itemLocalizationEnabledAndForeign) { @@ -461,6 +471,12 @@ protected override void OnInitialize() { this.ShowForeignItems = false; } + + if (this.BrowserUrl != null) + { + this.BrowserUrlDynamicValuesHelper = new DynamicValuesHelper(this.BrowserUrl); + this.BrowserUrlDynamicValuesHelper.Initialize(this); + } } diff --git a/Composite/C1Console/Trees/Foundation/TreeNodeCreatorFactory.cs b/Composite/C1Console/Trees/Foundation/TreeNodeCreatorFactory.cs index 058aee1c12..d9bd9c5c85 100644 --- a/Composite/C1Console/Trees/Foundation/TreeNodeCreatorFactory.cs +++ b/Composite/C1Console/Trees/Foundation/TreeNodeCreatorFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Collections.Generic; using System.Xml.Linq; @@ -134,6 +134,7 @@ private static TreeNode BuildDataElementsTreeNode(XElement element, Tree tree) XAttribute openedIconAttribute = element.Attribute("OpenedIcon"); XAttribute showForeignItemsAttribute = element.Attribute("ShowForeignItems"); XAttribute leafDisplayAttribute = element.Attribute("Display"); + XAttribute browserUrlAttribute = element.Attribute("BrowserUrl"); if (typeAttribute == null) { @@ -170,8 +171,9 @@ private static TreeNode BuildDataElementsTreeNode(XElement element, Tree tree) Icon = icon, OpenedIcon = openedIcon, ShowForeignItems = showForeignItemsAttribute.GetValueOrDefault("true").ToLowerInvariant() == "true", - Display = leafDisplay - }; + Display = leafDisplay, + BrowserUrl = browserUrlAttribute.GetValueOrDefault(null) + }; List treeNodes; if (tree.BuildProcessContext.DataInteraceToTreeNodes.TryGetValue(interfaceType, out treeNodes) == false) @@ -229,6 +231,7 @@ private static TreeNode BuildSimpleElementTreeNode(XElement element, Tree tree) XAttribute toolTipAttribute = element.Attribute("ToolTip"); XAttribute iconAttribute = element.Attribute("Icon"); XAttribute openedIconAttribute = element.Attribute("OpenedIcon"); + XAttribute browserUrlAttribute = element.Attribute("BrowserUrl"); if (idAttribute == null) { @@ -271,8 +274,9 @@ private static TreeNode BuildSimpleElementTreeNode(XElement element, Tree tree) Label = labelAttribute.Value, ToolTip = toolTipAttribute.GetValueOrDefault(labelAttribute.Value), Icon = icon, - OpenIcon = openedIcon - }; + OpenIcon = openedIcon, + BrowserUrl = browserUrlAttribute.GetValueOrDefault(null) + }; } private static TreeNode BuildRootTreeNode(XElement element, Tree tree) diff --git a/Composite/C1Console/Trees/SimpleElementTreeNode.cs b/Composite/C1Console/Trees/SimpleElementTreeNode.cs index 024b497910..c76fc3c26d 100644 --- a/Composite/C1Console/Trees/SimpleElementTreeNode.cs +++ b/Composite/C1Console/Trees/SimpleElementTreeNode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Composite.C1Console.Elements; @@ -15,6 +15,7 @@ internal class SimpleElementTreeNode : TreeNode public string ToolTip { get; internal set; } // Defaults to Label public ResourceHandle Icon { get; internal set; } // Defaults to 'folder' public ResourceHandle OpenIcon { get; internal set; } // Defaults to 'open-folder' or if Icon is set, then, Icon + public string BrowserUrl { get; internal set; } // Defaults to no URL, what will be shows in console browser on focus // Cached values internal DynamicValuesHelper LabelDynamicValuesHelper { get; set; } @@ -80,6 +81,13 @@ protected override IEnumerable OnGetElements(EntityToken parentEntityTo )); + if (this.BrowserUrl != null) + { + element.PropertyBag.Add("BrowserUrl", this.BrowserUrl); + element.PropertyBag.Add("BrowserToolingOn", "false"); + } + + if (parentEntityToken is TreePerspectiveEntityToken) { element.ElementHandle.Piggyback[StringConstants.PiggybagTreeId] = Tree.TreeId; diff --git a/Website/Composite/schemas/Trees/Tree.xsd b/Website/Composite/schemas/Trees/Tree.xsd index 8cb5bb4bc8..816bb33651 100644 --- a/Website/Composite/schemas/Trees/Tree.xsd +++ b/Website/Composite/schemas/Trees/Tree.xsd @@ -1,4 +1,4 @@ - + - + + + Custom URL to display in the console browser when this element is focused. + + + + @@ -942,7 +948,14 @@ - + + + + Custom URL for each data item, to display in the console browser when this element is focused. Use ${C1:Data:[TypeName]:[FieldName]} to get a field value of a parent (or self) data element. + + + + From ed1a0d99105a2f2af34180d56ce220bd018a44f7 Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 14 Aug 2018 15:31:20 +0200 Subject: [PATCH 08/51] Tree XML BrowserUrl feature now supporting ~/ based URLs (app local). --- Composite/C1Console/Trees/CustomUrlActionNode.cs | 2 +- Composite/C1Console/Trees/DataElementsTreeNode.cs | 9 ++++++++- Composite/C1Console/Trees/SimpleElementTreeNode.cs | 9 ++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Composite/C1Console/Trees/CustomUrlActionNode.cs b/Composite/C1Console/Trees/CustomUrlActionNode.cs index 9e63487733..c567d9d9db 100644 --- a/Composite/C1Console/Trees/CustomUrlActionNode.cs +++ b/Composite/C1Console/Trees/CustomUrlActionNode.cs @@ -46,7 +46,7 @@ protected override void OnAddAction(Action actionAdder, EntityTok { string url = this.UrlDynamicValuesHelper.ReplaceValues(dynamicValuesHelperReplaceContext); - this.External = url.Contains("://"); + this.External = url.Contains("//"); if(!External) { diff --git a/Composite/C1Console/Trees/DataElementsTreeNode.cs b/Composite/C1Console/Trees/DataElementsTreeNode.cs index 27159de61d..d19bd4d37b 100644 --- a/Composite/C1Console/Trees/DataElementsTreeNode.cs +++ b/Composite/C1Console/Trees/DataElementsTreeNode.cs @@ -359,7 +359,14 @@ EntityToken parentEntityToken if (this.BrowserUrl != null) { - element.PropertyBag.Add("BrowserUrl", this.BrowserUrlDynamicValuesHelper.ReplaceValues(replaceContext)); + var url = this.BrowserUrlDynamicValuesHelper.ReplaceValues(replaceContext); + + if (!url.Contains("//")) + { + url = Core.WebClient.UrlUtils.ResolvePublicUrl(url); + } + + element.PropertyBag.Add("BrowserUrl", url); element.PropertyBag.Add("BrowserToolingOn", "false"); } diff --git a/Composite/C1Console/Trees/SimpleElementTreeNode.cs b/Composite/C1Console/Trees/SimpleElementTreeNode.cs index c76fc3c26d..76472eb688 100644 --- a/Composite/C1Console/Trees/SimpleElementTreeNode.cs +++ b/Composite/C1Console/Trees/SimpleElementTreeNode.cs @@ -83,7 +83,14 @@ protected override IEnumerable OnGetElements(EntityToken parentEntityTo if (this.BrowserUrl != null) { - element.PropertyBag.Add("BrowserUrl", this.BrowserUrl); + var url = this.BrowserUrl; + + if (!url.Contains("//")) + { + url = Core.WebClient.UrlUtils.ResolvePublicUrl(url); + } + + element.PropertyBag.Add("BrowserUrl", url); element.PropertyBag.Add("BrowserToolingOn", "false"); } From 36283a8a74b4788e470b058b571f295f51b1ed03 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 23 Aug 2018 11:41:16 -0400 Subject: [PATCH 09/51] Added search and replace to visual editor --- .../visualeditor/includes/toolbarsimple.inc | 1 + .../VisualSearchAndReplace.js | 238 ++++++++++++++++++ .../compositesearchandreplace/plugin.min.js | 88 +++++++ .../visualsearchandreplace.aspx | 76 ++++++ .../plugins/searchreplace/plugin.min.js | 1 + .../misc/editors/visualeditor/visualeditor.js | 2 +- .../Composite.Web.VisualEditor.en-us.xml | 13 + 7 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/VisualSearchAndReplace.js create mode 100644 Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/plugin.min.js create mode 100644 Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/visualsearchandreplace.aspx create mode 100644 Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/searchreplace/plugin.min.js diff --git a/Website/Composite/content/misc/editors/visualeditor/includes/toolbarsimple.inc b/Website/Composite/content/misc/editors/visualeditor/includes/toolbarsimple.inc index 49c4e63d3d..37b939d3fc 100644 --- a/Website/Composite/content/misc/editors/visualeditor/includes/toolbarsimple.inc +++ b/Website/Composite/content/misc/editors/visualeditor/includes/toolbarsimple.inc @@ -14,6 +14,7 @@ + diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/VisualSearchAndReplace.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/VisualSearchAndReplace.js new file mode 100644 index 0000000000..4a96a9827e --- /dev/null +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/VisualSearchAndReplace.js @@ -0,0 +1,238 @@ + + +VisualSearchAndReplace.prototype = new DialogPageBinding; +VisualSearchAndReplace.prototype.constructor = VisualSearchAndReplace; +VisualSearchAndReplace.superclass = DialogPageBinding.prototype; + +function VisualSearchAndReplace() { + + //debugger; + this.logger = SystemLogger.getLogger("VisualSearchAndReplace"); + this._findText = null; + this._replacementText = null; + this._caseSensitive = null; + this._matchWholeWord = null; + this._editor = null; + this._searchReplacePlugin = null; + this._findTextBox = null; + this._replaceTextBox = null; + this._caseSensitiveCheckBox = null; + this._initialized = false; + this._matchWholeWordCheckbox = null; + this._itemsFound = 0; +} + +VisualSearchAndReplace.prototype.toString = function () { + //debugger; + return "[VisualSearchAndReplace]"; +} + +/** +* @overloads {SystemTreeBinding#onBindingRegister} +*/ +VisualSearchAndReplace.prototype.onBindingRegister = function () { + VisualSearchAndReplace.superclass.onBindingRegister.call(this); +} + +VisualSearchAndReplace.prototype.setPageArgument = function (arg) { + //debugger; + VisualSearchAndReplace.superclass.setPageArgument.call(this); + + var self = this; + + this._editor = arg.editor; + this._searchReplacePlugin = arg.plugin; + this._findText = ""; + + var selectedText = this._editor.selection.getSel().toString(); + if (selectedText) + this._findText = selectedText; + + this._replacementText = ""; + + var map = this.bindingWindow.bindingMap; + + /* + * Locate key players. + */ + this._findTextBox = map.searchFor; + + self.updateMessage(""); + + this._findTextBox.bindingElement.addEventListener("change", function () { + self.updateMessage(""); + self._itemsFound = 0; + self.setButtonStates(); + }); + + this._replaceTextBox = map.replaceWith; + + this._caseSensitiveCheckBox = map.matchCase; + this._matchWholeWordCheckbox = map.matchWholeWord; + + this._findTextBox.setValue(this._findText); + this._replaceTextBox.setValue(this._replacementText); + + this._initialized = true; + + this.setButtonStates(); +} + +VisualSearchAndReplace.prototype.setButtonStates = function () { + + var map = this.bindingWindow.bindingMap; + + if (this.stateChanged() || this._itemsFound <= 0) { + map.broadcasterFind.setDisabled(false); + map.broadcasterReplace.setDisabled(true); + map.broadcasterReplaceAll.setDisabled(true); + map.broadcasterPrev.setDisabled(true); + map.broadcasterNext.setDisabled(true); + } + else { + map.broadcasterFind.setDisabled(false); + map.broadcasterReplace.setDisabled(false); + map.broadcasterReplaceAll.setDisabled(false); + map.broadcasterPrev.setDisabled(false); + map.broadcasterNext.setDisabled(false); + } +} + +VisualSearchAndReplace.prototype.stateChanged = function () { + + return (this._findTextBox != null && this._findTextBox.getValue() != this._findText) || this.getChecked(this._caseSensitiveCheckBox) !== this._caseSensitive || this.getChecked(this._matchWholeWordCheckbox) !== this._matchWholeWord; +} + +VisualSearchAndReplace.prototype.getChecked = function(control) +{ + if (control != null) { + if (control.getValue() === true) + return true; + + if (control.getValue() == "on") + return true; + } + return false; +} + +VisualSearchAndReplace.prototype.handleAction = function (action) { + + VisualSearchAndReplace.superclass.handleAction.call(this, action); + + if (action.type == ButtonBinding.ACTION_COMMAND) { + + var binding = action.target; + var id = binding.bindingElement.id; + + this._replacementText = this._replaceTextBox.getValue(); + + var foundItem = false; + + switch (id) { + case "buttonFind": + this.findNext(); + action.consume(); + break; + case "buttonReplace": + this.replaceText(); + action.consume(); + break; + case "buttonReplaceAll": + this.replaceAllFoundText(); + action.consume(); + break; + case "buttonNext": + this.moveNext(); + action.consume(); + break; + case "buttonPrev": + this.movePrev(); + action.consume(); + break; + } + } +} + +/** +* Implements {IBroadcastListener} +* @param {string} broadcast +* @param {object} arg +*/ +VisualSearchAndReplace.prototype.handleBroadcast = function (broadcast, arg) { + + VisualSearchAndReplace.superclass.handleBroadcast.call(this, broadcast, arg); +} + +VisualSearchAndReplace.prototype.findNext = function () { + + if (this.stateChanged()) { + this.setOptionsFromUserInput(); + } + + var result = this._searchReplacePlugin.find(this._findText, this._caseSensitive, this._matchWholeWord); + + if (result <= 0) { + this.updateMessage("nothing was found"); + } + else { + this.updateMessage(result + " item(s) were found"); + } + + this._itemsFound = result; + this.setButtonStates(); +} + +VisualSearchAndReplace.prototype.replaceText = function () { + + this.setOptionsFromUserInput(); + this._searchReplacePlugin.replace(this._replacementText); +} + +VisualSearchAndReplace.prototype.updateMessage = function (message) { + var el = document.getElementById("nothingWasFound"); + if (el != null) + el.innerHTML = message; +} + + +VisualSearchAndReplace.prototype.replaceAllFoundText = function () { + + this.setOptionsFromUserInput(); + this._searchReplacePlugin.replace(this._replacementText, true, true); +} + +VisualSearchAndReplace.prototype.moveNext = function () { + + this.setOptionsFromUserInput(); + this._searchReplacePlugin.next(); +} + +VisualSearchAndReplace.prototype.movePrev = function () { + + this.setOptionsFromUserInput(); + this._searchReplacePlugin.prev(); +} + +VisualSearchAndReplace.prototype.setOptionsFromUserInput = function () { + + this._caseSensitive = this.getChecked(this._caseSensitiveCheckBox); + this._matchWholeWord = this.getChecked(this._matchWholeWordCheckbox); + this._findText = this._findTextBox.getValue(); + this._replacementText = this._replaceTextBox.getValue(); + return; +} + +VisualSearchAndReplace.prototype.onBeforePageInitialize = function () { + VisualSearchAndReplace.superclass.onBeforePageInitialize.call(this); +} + +VisualSearchAndReplace.prototype.onDeactivate = function () { + this._searchReplacePlugin.done(); + VisualSearchAndReplace.superclass.onDeactivate.call(this); +} + + + + + + diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/plugin.min.js new file mode 100644 index 0000000000..53ba84585b --- /dev/null +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/plugin.min.js @@ -0,0 +1,88 @@ +/** + * Composite plugin. + */ +new function () { + + var SEARCH_REPLACE_URL = "${tiny}/plugins/compositesearchandreplace/visualsearchandreplace.aspx"; + + var each = tinymce.each, Event = tinymce.dom.Event; + + tinymce.create("tinymce.plugins.CompositeSearchAndReplacePlugin", { + + /** + * @type {tinymce.Editor} + */ + editor: null, + + /** + * Get info + */ + getInfo: function () { + return { + longname: "Composite Search and Replace Plugin", + author: "Peter Edwards", + authorurl: "https://www.iihs.org/", + infourl: null, + version: tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + /** + * @param {tinymce.Editor} ed + * @param {string} url + */ + init: function (ed, url) { + this.editor = ed; + var self = this; + + //Remove searchreplace plugin shortcut and replace with composite search and replace shortcut + ed.shortcuts.remove('Meta+F'); + ed.shortcuts.add('Meta+F', '', function () { this.execCommand("compositeSearchAndReplace"); }); + ed.shortcuts.add('Meta+H', '', function () { this.execCommand("compositeSearchAndReplace"); }); + }, + + /** + * @param {string} cmd + * @param {boolean} ui + * @param {string} value + */ + execCommand: function (cmd, ui, value) { + + var result = false; + var self = this; + var editor = this.editor; + var editorBinding = editor.theme.editorBinding; + + if (cmd == "compositeSearchAndReplace") { + //this._insertComponent(); + this._searchAndReplaceDialog(); + editorBinding.checkForDirty(); + result = true; + } + return result; + }, + + _searchAndReplaceDialog: function () { + + this.editor.theme.enableDialogMode(); + var self = this; + var plugin = this.editor.plugins.searchreplace; + + var self = this; + var handler = { + handleDialogResponse: function (response, result) { + self.editor.theme.disableDialogMode(); + } + }; + var args = { + editor: this.editor, + plugin: plugin + }; + Dialog.invokeModal(SEARCH_REPLACE_URL, handler, args); + return true; + } + }); + + // Register plugin + tinymce.PluginManager.add("compositesearchandreplace", tinymce.plugins.CompositeSearchAndReplacePlugin); +}; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/visualsearchandreplace.aspx b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/visualsearchandreplace.aspx new file mode 100644 index 0000000000..aa79dee5d4 --- /dev/null +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/visualsearchandreplace.aspx @@ -0,0 +1,76 @@ + + +<%@ Page Language="C#" %> + + + + ${string:Composite.Web.VisualEditor:SearchAndReplace.LabelTitle} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/searchreplace/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/searchreplace/plugin.min.js new file mode 100644 index 0000000000..7ec3f86e0d --- /dev/null +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/searchreplace/plugin.min.js @@ -0,0 +1 @@ +!function(){function e(e){return e&&1==e.nodeType&&"false"===e.contentEditable}function t(t,n,r,i,o){function a(e,t){if(t=t||0,!e[0])throw"findAndReplaceDOMText cannot handle zero-length matches";var n=e.index;if(t>0){var r=e[t];if(!r)throw"Invalid capture group";n+=e[0].indexOf(r),e[0]=r}return[n,n+e[0].length,[e[0]]]}function s(t){var n;if(3===t.nodeType)return t.data;if(m[t.nodeName]&&!p[t.nodeName])return"";if(n="",e(t))return"\n";if((p[t.nodeName]||g[t.nodeName])&&(n+="\n"),t=t.firstChild)do n+=s(t);while(t=t.nextSibling);return n}function l(t,n,r){var i,o,a,s,l=[],c=0,u=t,d=n.shift(),f=0;e:for(;;){if((p[u.nodeName]||g[u.nodeName]||e(u))&&c++,3===u.nodeType&&(!o&&u.length+c>=d[1]?(o=u,s=d[1]-c):i&&l.push(u),!i&&u.length+c>d[0]&&(i=u,a=d[0]-c),c+=u.length),i&&o){if(u=r({startNode:i,startNodeIndex:a,endNode:o,endNodeIndex:s,innerNodes:l,match:d[2],matchIndex:f}),c-=o.length-s,i=null,o=null,l=[],d=n.shift(),f++,!d)break}else if(m[u.nodeName]&&!p[u.nodeName]||!u.firstChild){if(u.nextSibling){u=u.nextSibling;continue}}else if(!e(u)){u=u.firstChild;continue}for(;;){if(u.nextSibling){u=u.nextSibling;break}if(u.parentNode===t)break e;u=u.parentNode}}}function c(e){var t;if("function"!=typeof e){var n=e.nodeType?e:f.createElement(e);t=function(e,t){var r=n.cloneNode(!1);return r.setAttribute("data-mce-index",t),e&&r.appendChild(f.createTextNode(e)),r}}else t=e;return function(e){var n,r,i,o=e.startNode,a=e.endNode,s=e.matchIndex;if(o===a){var l=o;i=l.parentNode,e.startNodeIndex>0&&(n=f.createTextNode(l.data.substring(0,e.startNodeIndex)),i.insertBefore(n,l));var c=t(e.match[0],s);return i.insertBefore(c,l),e.endNodeIndex0}var u=this,d=-1;u.init=function(e){e.addMenuItem("searchreplace",{text:"Find and replace",shortcut:"Meta+F",onclick:n,separator:"before",context:"edit"}),e.addButton("searchreplace",{tooltip:"Find and replace",shortcut:"Meta+F",onclick:n}),e.addCommand("SearchReplace",n),e.shortcuts.add("Meta+F","",n)},u.find=function(e,t,n){e=e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&"),e=n?"\\b"+e+"\\b":e;var r=i(new RegExp(e,t?"g":"gi"));return r&&(d=-1,d=s(!0)),r},u.next=function(){var e=s(!0);e!==-1&&(d=e)},u.prev=function(){var e=s(!1);e!==-1&&(d=e)},u.replace=function(t,n,i){var s,f,p,m,g,h,v=d;for(n=n!==!1,p=e.getBody(),f=tinymce.grep(tinymce.toArray(p.getElementsByTagName("span")),c),s=0;sd&&f[s].setAttribute("data-mce-index",g-1)}return e.undoManager.add(),d=v,n?(h=a(v+1).length>0,u.next()):(h=a(v-1).length>0,u.prev()),!i&&h},u.done=function(t){var n,i,a,s;for(i=tinymce.toArray(e.getBody().getElementsByTagName("span")),n=0;n + + + + + + + + + + + + + From f9fce48b1658eef347c55a92ac5f8120b99cd359 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 23 Aug 2018 16:39:18 -0400 Subject: [PATCH 10/51] Added find and replace to codemirror --- ...ditorFindAndReplaceToolBarButtonBinding.js | 121 ++++++++++ ...ditorToggleWordWrapToolbarButtonBinding.js | 96 ++++++++ .../editors/codemirroreditor/codemirror.aspx | 1 + .../codemirroreditor/codemirroreditor.aspx | 8 + .../codemirrorfindandreplace.aspx | 61 +++++ .../codemirrorfindandreplace.js | 215 ++++++++++++++++++ .../Composite.Web.SourceEditor.en-us.xml | 12 + Website/gruntfile.js | 7 +- 8 files changed, 518 insertions(+), 3 deletions(-) create mode 100644 Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorFindAndReplaceToolBarButtonBinding.js create mode 100644 Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorToggleWordWrapToolbarButtonBinding.js create mode 100644 Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx create mode 100644 Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.js diff --git a/Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorFindAndReplaceToolBarButtonBinding.js b/Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorFindAndReplaceToolBarButtonBinding.js new file mode 100644 index 0000000000..aa0facad73 --- /dev/null +++ b/Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorFindAndReplaceToolBarButtonBinding.js @@ -0,0 +1,121 @@ +SourceEditorFindAndReplaceToolBarButtonBinding.prototype = new EditorToolBarButtonBinding; +SourceEditorFindAndReplaceToolBarButtonBinding.prototype.constructor = SourceEditorFindAndReplaceToolBarButtonBinding; +SourceEditorFindAndReplaceToolBarButtonBinding.superclass = EditorToolBarButtonBinding.prototype; + +/** +* @class +*/ +function SourceEditorFindAndReplaceToolBarButtonBinding() { + + /** + * @type {SystemLogger} + */ + this.logger = SystemLogger.getLogger("SourceEditorFindAndReplaceToolBarButtonBinding"); + + /** + * The containing editor. + * @type {SourceEditorBinding} + */ + this._editorBinding = null; + + /** + * The codemirror editor. + * @type {Codemirror} + */ + this._codemirrorEditor = null; + + /* + * Returnable. + */ + return this; +} + +/** +* Identifies binding. +*/ +SourceEditorFindAndReplaceToolBarButtonBinding.prototype.toString = function() { + + return "[SourceEditorFindAndReplaceToolBarButtonBinding]"; +}; + +/** +* @overloads {EditorToolBarBinding#onBindingAttach} +*/ +SourceEditorFindAndReplaceToolBarButtonBinding.prototype.onBindingAttach = function() { + + SourceEditorFindAndReplaceToolBarButtonBinding.superclass.onBindingAttach.call(this); + var codemirrorwindow = this.bindingWindow.bindingMap.codemirrorwindow; + EditorBinding.registerComponent(this, codemirrorwindow); +}; + +/** +* @implements {IWysiwygEditorComponent} +* @param {CodemirrorEditorBinding} binding +* @param {Codemirror} editor +*/ +SourceEditorFindAndReplaceToolBarButtonBinding.prototype.initializeSourceEditorComponent = function(binding, editor) { + + debugger; + + this._editorBinding = binding; + this._codemirrorEditor = editor; + + var self = this; + + this._codemirrorEditor.addKeyMap({ + "Ctrl-F": function () { + self.openDialog(self); + } + }); + + this._codemirrorEditor.addKeyMap({ + "Cmd-F": function () { + self.openDialog(self); + } + }); + +}; + +/** +* Open find and replace dialog and wait for input. +* @overwrites {ToolBarButtonBinding#onCommand} +*/ +SourceEditorFindAndReplaceToolBarButtonBinding.prototype.oncommand = function() { + this.openDialog(this); +}; + +SourceEditorFindAndReplaceToolBarButtonBinding.prototype.openDialog = function (self) { + + var handler = { + handleDialogResponse: function (response, result) { + } + }; + + var args = { editor: this._codemirrorEditor }; + Dialog.invokeModal("${root}/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx", handler, args); +} + +/** +* This has been isolated so that the contextmenu can invoke it. +* @param {string} cmd +* @param {string} gui +* @param {string} val +*/ +SourceEditorFindAndReplaceToolBarButtonBinding.prototype.handleCommand = function(cmd, gui, val) { + + this.oncommand(); +}; + +/** +* @param {string} string +* @param {string} token +* @return {string} +*/ +SourceEditorFindAndReplaceToolBarButtonBinding.prototype._getStartString = function(string, token) { + + var result = null; + if (string.indexOf(token) > -1) { + result = string.substring(0, string.indexOf(token)); + } + return result; +}; diff --git a/Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorToggleWordWrapToolbarButtonBinding.js b/Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorToggleWordWrapToolbarButtonBinding.js new file mode 100644 index 0000000000..17cecaa036 --- /dev/null +++ b/Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorToggleWordWrapToolbarButtonBinding.js @@ -0,0 +1,96 @@ +SourceEditorToggleWordWrapToolbarButtonBinding.prototype = new EditorToolBarButtonBinding; +SourceEditorToggleWordWrapToolbarButtonBinding.prototype.constructor = SourceEditorToggleWordWrapToolbarButtonBinding; +SourceEditorToggleWordWrapToolbarButtonBinding.superclass = EditorToolBarButtonBinding.prototype; + +/** + * @class + */ +function SourceEditorToggleWordWrapToolbarButtonBinding () { + + /** + * @type {SystemLogger} + */ + this.logger = SystemLogger.getLogger ( "SourceEditorToggleWordWrapToolbarButtonBinding" ); + + /** + * The containing editor. + * @type {SourceEditorBinding} + */ + this._editorBinding = null; + + /** + * The codemirror editor. + * @type {Codemirror} + */ + this._codemirrorEditor = null; + + /* + * Returnable. + */ + return this; +} + +/** + * Identifies binding. + */ +SourceEditorToggleWordWrapToolbarButtonBinding.prototype.toString = function () { + + return "[SourceEditorToggleWordWrapToolbarButtonBinding]"; +} + +/** + * @overloads {EditorToolBarBinding#onBindingAttach} + */ +SourceEditorToggleWordWrapToolbarButtonBinding.prototype.onBindingAttach = function () { + + SourceEditorToggleWordWrapToolbarButtonBinding.superclass.onBindingAttach.call ( this ); + var codemirrorwindow = this.bindingWindow.bindingMap.codemirrorwindow; + EditorBinding.registerComponent ( this, codemirrorwindow ); +} + +/** + * @implements {IWysiwygEditorComponent} + * @param {CodemirrorEditorBinding} binding + * @param {Codemirror} editor + */ +SourceEditorToggleWordWrapToolbarButtonBinding.prototype.initializeSourceEditorComponent = function ( binding, editor ) { + + this._editorBinding = binding; + this._codemirrorEditor = editor; +} + +/** + * + * @overwrites {ToolBarButtonBinding#onCommand} + */ +SourceEditorToggleWordWrapToolbarButtonBinding.prototype.oncommand = function () { + + var self = this; + this._codemirrorEditor.setOption("lineWrapping", !this._codemirrorEditor.getOption("lineWrapping")); + localStorage.setItem("lineWrapping", this._codemirrorEditor.getOption("lineWrapping")); +} + +/** + * This has been isolated so that the contextmenu can invoke it. + * @param {string} cmd + * @param {string} gui + * @param {string} val + */ +SourceEditorToggleWordWrapToolbarButtonBinding.prototype.handleCommand = function ( cmd, gui, val ) { + + this.oncommand (); +} + +/** + * @param {string} string + * @param {string} token + * @return {string} + */ +SourceEditorToggleWordWrapToolbarButtonBinding.prototype._getStartString = function ( string, token ) { + + var result = null; + if ( string.indexOf ( token ) > -1 ) { + result = string.substring ( 0, string.indexOf ( token )); + } + return result; +} diff --git a/Website/Composite/content/misc/editors/codemirroreditor/codemirror.aspx b/Website/Composite/content/misc/editors/codemirroreditor/codemirror.aspx index e5c3942d2e..c4378cc0ed 100644 --- a/Website/Composite/content/misc/editors/codemirroreditor/codemirror.aspx +++ b/Website/Composite/content/misc/editors/codemirroreditor/codemirror.aspx @@ -9,6 +9,7 @@ top.Application.declareTopLocal(window); + diff --git a/Website/Composite/content/misc/editors/codemirroreditor/codemirroreditor.aspx b/Website/Composite/content/misc/editors/codemirroreditor/codemirroreditor.aspx index c652d6fec4..0b0f08ded3 100644 --- a/Website/Composite/content/misc/editors/codemirroreditor/codemirroreditor.aspx +++ b/Website/Composite/content/misc/editors/codemirroreditor/codemirroreditor.aspx @@ -14,6 +14,8 @@ xmlns:control="http://www.composite.net/ns/uicontrol"> + + @@ -53,6 +55,12 @@ xmlns:control="http://www.composite.net/ns/uicontrol"> + + diff --git a/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx b/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx new file mode 100644 index 0000000000..c6b6893d92 --- /dev/null +++ b/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx @@ -0,0 +1,61 @@ + + + + + ${string:Composite.Web.SourceEditor:FindAndReplace.LabelTitle} + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.js b/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.js new file mode 100644 index 0000000000..ab150513c7 --- /dev/null +++ b/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.js @@ -0,0 +1,215 @@ + + +CodemirrorFindAndReplace.prototype = new DialogPageBinding; +CodemirrorFindAndReplace.prototype.constructor = CodemirrorFindAndReplace; +CodemirrorFindAndReplace.superclass = DialogPageBinding.prototype; + + +function CodemirrorFindAndReplace() { + + this._cursor = null; + this.logger = SystemLogger.getLogger("CodemirrorFindAndReplace"); + this._findText = null; + this._replacementText = null; + this._caseSensitive = null; + this._matchWholeWord = null; + this._editor = null; + this._findTextBox = null; + this._replaceTextBox = null; + this._caseSensitiveCheckbox = null; + this._initialized = false; + this._textPosition = null; + this._matchWholeWordCheckbox = null; +} + +CodemirrorFindAndReplace.prototype.toString = function () { + return "[CodemirrorFindAndReplace]"; +} + +CodemirrorFindAndReplace.prototype.onBindingRegister = function () { + CodemirrorFindAndReplace.superclass.onBindingRegister.call(this); +} + +CodemirrorFindAndReplace.prototype.setPageArgument = function (arg) { + + CodemirrorFindAndReplace.superclass.setPageArgument.call(this); + + this._editor = arg.editor; + this._findText = this._editor.getSelection(); + this._replacementText = ""; + + var map = this.bindingWindow.bindingMap; + + /* + * Locate key players. + */ + this._findTextBox = map.searchFor; + this._replaceTextBox = map.replaceWith; + this._caseSensitiveCheckbox = map.matchCase; + this._matchWholeWordCheckbox = map.matchWholeWord; + + + if (!(app.FindAndReplaceOverride == undefined) && app.FindAndReplaceOverride.hasData()) { + this._findTextBox.setValue(app.FindAndReplaceOverride.findText); + this._replaceTextBox.setValue(app.FindAndReplaceOverride.replaceText); + + if (app.FindAndReplaceOverride.matchCase) + this._caseSensitiveCheckbox.check(app.FindAndReplaceOverride.matchCase); + + if (app.FindAndReplaceOverride.matchWholeWord) + this._matchWholeWordCheckbox.check(app.FindAndReplaceOverride.matchWholeWord); + } + else { + this._findTextBox.setValue(this._findText); + this._replaceTextBox.setValue(this._replacementText); + } + + this._initialized = true; + map.broadcasterReplace.setDisabled(true); +} + +CodemirrorFindAndReplace.prototype.getChecked = function (control) { + if (control != null) { + if (control.getValue() === true) + return true; + + if (control.getValue() == "on") + return true; + } + return false; +} + +CodemirrorFindAndReplace.prototype.stateChanged = function () { + + return (this._findTextBox != null && this._findTextBox.getValue() != this._findText) + || this._cursor == null + || this.getChecked(this._caseSensitiveCheckbox) !== this._caseSensitive + || this.getChecked(this._matchWholeWordCheckbox) !== this._matchWholeWord + || this._cursor.to() != this._textPosition; + +} + +CodemirrorFindAndReplace.prototype.handleAction = function (action) { + + CodemirrorFindAndReplace.superclass.handleAction.call(this, action); + + if (action.type == ButtonBinding.ACTION_COMMAND) { + + var binding = action.target; + var id = binding.bindingElement.id; + + this._replacementText = this._replaceTextBox.getValue(); + + var foundItem = false; + + switch (id) { + case "buttonFindNext": + this.findNextText(); + action.consume(); + break; + case "buttonReplace": + this.replaceText(); + action.consume(); + break; + case "buttonReplaceAll": + this.replaceAllFoundText(); + action.consume(); + break; + } + } +} + +/** +* Implements {IBroadcastListener} +* @param {string} broadcast +* @param {object} arg +*/ +CodemirrorFindAndReplace.prototype.handleBroadcast = function (broadcast, arg) { + + CodemirrorFindAndReplace.superclass.handleBroadcast.call(this, broadcast, arg); +} + +CodemirrorFindAndReplace.prototype.findNextText = function () { + + if (this.stateChanged()) { + + this.setOptionsFromUserInput(); + + this._cursor = this._editor.getSearchCursor(this._findText, this._textPosition, this._caseSensitive); + } + + var map = this.bindingWindow.bindingMap; + var foundItem = this._cursor.findNext(); + if (foundItem) { + + this._editor.setSelection(this._cursor.from(), this._cursor.to()); + map.broadcasterReplace.setDisabled(false); + this._textPosition = this._cursor.to() + + } + else { + this._textPosition = false; + map.broadcasterReplace.setDisabled(true); + } + +} + +CodemirrorFindAndReplace.prototype.replaceText = function () { + this._cursor.replace(this._replacementText); + this.findNextText(); + this._textPosition = this._cursor.to(); +} + + +CodemirrorFindAndReplace.prototype.replaceAllFoundText = function () { + + this.setOptionsFromUserInput(); + + var infiniteGuard = 0; + this._cursor = this._editor.getSearchCursor(this._findText, false, this._caseSensitive); + while (this._cursor.findNext() && infiniteGuard <= 200) { + var newStart = this._cursor.to(); + this._cursor.replace(this._replacementText); + newStart.character = newStart.character + this._replacementText.length; + this._cursor = this._editor.getSearchCursor(this._findText, newStart, this._caseSensitive); + infiniteGuard = infiniteGuard + 1; + } + + if (infiniteGuard >= 200) + alert("Error: Too many iterations occurred in the replaceAllFoundText method of CodemirrorFindAndReplace"); + +} + +CodemirrorFindAndReplace.prototype.setOptionsFromUserInput = function () { + + if (this._textPosition == null) + this._textPosition = this._editor.getCursor(); + + this._caseSensitive = this.getChecked(this._caseSensitiveCheckbox); + this._matchWholeWord = this.getChecked(this._matchWholeWordCheckbox); + + if (this._matchWholeWord == true) { + this._findText = new RegExp("\\b" + this._findTextBox.getValue() + "\\b", this._caseSensitive ? "" : "i"); + } + else { + this._findText = new RegExp(this._findTextBox.getValue(), this._caseSensitive ? "" : "i"); + } + + return; +} + +function _CodemirrorFindAndReplace() { } + +_CodemirrorFindAndReplace.prototype = { + findText: "", + replaceText: "", + matchCase: false, + matchWholeWord: false, + hasData: function () { + return this.findText !== "" || this.replaceText !== ""; + } +} + + + + diff --git a/Website/Composite/localization/Composite.Web.SourceEditor.en-us.xml b/Website/Composite/localization/Composite.Web.SourceEditor.en-us.xml index d6976323f3..7c9015c3b7 100644 --- a/Website/Composite/localization/Composite.Web.SourceEditor.en-us.xml +++ b/Website/Composite/localization/Composite.Web.SourceEditor.en-us.xml @@ -44,4 +44,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Website/gruntfile.js b/Website/gruntfile.js index b4678f5b1c..0ac2289659 100644 --- a/Website/gruntfile.js +++ b/Website/gruntfile.js @@ -1,4 +1,4 @@ -/// +/// module.exports = function (grunt) { 'use strict'; @@ -24,7 +24,8 @@ module.exports = function (grunt) { codemirror: { files: [ { expand: true, cwd: 'bower_components/codemirror/addon/mode', src: ['*.*'], dest: 'Composite/lib/codemirror/addon/mode' }, - { expand: true, cwd: 'bower_components/codemirror/addon/selection', src: ['*.*'], dest: 'Composite/lib/codemirror/addon/selection' }, + { expand: true, cwd: 'bower_components/codemirror/addon/selection', src: ['*.*'], dest: 'Composite/lib/codemirror/addon/selection' }, + { expand: true, cwd: 'bower_components/codemirror/addon/search', src: ['*.*'], dest: 'Composite/lib/codemirror/addon/search' }, { expand: true, cwd: 'bower_components/codemirror/lib', src: ['*.*'], dest: 'Composite/lib/codemirror/lib' }, { expand: true, cwd: 'bower_components/codemirror/mode/clike', src: ['*.*'], dest: 'Composite/lib/codemirror/mode/clike' }, { expand: true, cwd: 'bower_components/codemirror/mode/css', src: ['*.*'], dest: 'Composite/lib/codemirror/mode/css' }, @@ -54,7 +55,7 @@ module.exports = function (grunt) { tinymce: { files: function () { let tinymceDestFolder = "Composite/content/misc/editors/visualeditor/tinymce"; - let tinymcePlugins = ["autolink", "lists", "paste", "table"]; + let tinymcePlugins = ["autolink", "lists", "paste", "table", "searchreplace"]; let tinymceFiles = [{ expand: true, cwd: 'bower_components/tinymce', src: ['tinymce.min.js'], dest: `${tinymceDestFolder}` }]; tinymcePlugins.forEach(function (pluginName, index) { tinymceFiles.push({ expand: true, cwd: `bower_components/tinymce/plugins/${pluginName}`, src: ['*.min.js'], dest: `${tinymceDestFolder}/plugins/${pluginName}` }); From 86222c7f9fb88fa5c06e3f38f0833639fb3d8d45 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Thu, 23 Aug 2018 16:51:22 -0400 Subject: [PATCH 11/51] Saving line wrapping settings --- .../misc/editors/codemirroreditor/codemirror.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Website/Composite/content/misc/editors/codemirroreditor/codemirror.js b/Website/Composite/content/misc/editors/codemirroreditor/codemirror.js index 71fe562b5f..cedd11affe 100644 --- a/Website/Composite/content/misc/editors/codemirroreditor/codemirror.js +++ b/Website/Composite/content/misc/editors/codemirroreditor/codemirror.js @@ -9,9 +9,16 @@ window.onload = function () { indentUnit: 4, indentWithTabs: true, extraKeys: {"Tab": "indentMore", "Shift-Tab": "indentLess"}, - lineNumbers: true, - theme: "composite" - }); + lineNumbers: true, + theme: "composite", + lineWrapping: false + }); + + if (localStorage.getItem("lineWrapping") == null) { + editor.setOption("lineWrapping", true); + } else { + editor.setOption("lineWrapping", localStorage.getItem("lineWrapping") === 'true'); + } var broadcaster = top.EventBroadcaster; var messages = top.BroadcastMessages; From bbb6a6048901c07692847f3a55cdab7d6dc41de9 Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Mon, 27 Aug 2018 15:18:32 -0400 Subject: [PATCH 12/51] Making some layout and small functional changes --- .../codemirroreditor/codemirroreditor.aspx | 4 +- .../codemirrorfindandreplace.aspx | 49 ++++++++++--- .../visualeditor/includes/toolbarsimple.inc | 4 +- .../VisualSearchAndReplace.js | 21 +++--- .../visualsearchandreplace.aspx | 69 ++++++++++++++----- .../Composite.Web.SourceEditor.en-us.xml | 2 + .../Composite.Web.VisualEditor.en-us.xml | 8 +-- 7 files changed, 110 insertions(+), 47 deletions(-) diff --git a/Website/Composite/content/misc/editors/codemirroreditor/codemirroreditor.aspx b/Website/Composite/content/misc/editors/codemirroreditor/codemirroreditor.aspx index 0b0f08ded3..3ce532560e 100644 --- a/Website/Composite/content/misc/editors/codemirroreditor/codemirroreditor.aspx +++ b/Website/Composite/content/misc/editors/codemirroreditor/codemirroreditor.aspx @@ -55,10 +55,10 @@ xmlns:control="http://www.composite.net/ns/uicontrol"> - - diff --git a/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx b/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx index c6b6893d92..04988f7dc1 100644 --- a/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx +++ b/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx @@ -6,6 +6,29 @@ + + +
@@ -19,32 +42,36 @@ - + - + - - + + +
+
+ - + - - +
+
+ - + - - - + +
+
diff --git a/Website/Composite/content/misc/editors/visualeditor/includes/toolbarsimple.inc b/Website/Composite/content/misc/editors/visualeditor/includes/toolbarsimple.inc index 37b939d3fc..c3ff07fd56 100644 --- a/Website/Composite/content/misc/editors/visualeditor/includes/toolbarsimple.inc +++ b/Website/Composite/content/misc/editors/visualeditor/includes/toolbarsimple.inc @@ -12,9 +12,9 @@ + - - + diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/VisualSearchAndReplace.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/VisualSearchAndReplace.js index 4a96a9827e..01ce87fa68 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/VisualSearchAndReplace.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/VisualSearchAndReplace.js @@ -166,20 +166,23 @@ VisualSearchAndReplace.prototype.handleBroadcast = function (broadcast, arg) { VisualSearchAndReplace.prototype.findNext = function () { if (this.stateChanged()) { - this.setOptionsFromUserInput(); - } + this.setOptionsFromUserInput(); + + var result = this._searchReplacePlugin.find(this._findText, this._caseSensitive, this._matchWholeWord); - var result = this._searchReplacePlugin.find(this._findText, this._caseSensitive, this._matchWholeWord); + if (result <= 0) { + this.updateMessage(StringBundle.getString("Composite.Web.VisualEditor", "SearchAndReplace.NothingFoundMessage")); + } + else { + this.updateMessage(result + " " + StringBundle.getString("Composite.Web.VisualEditor", "SearchAndReplace.ItemsWereFoundMessage")); + } - if (result <= 0) { - this.updateMessage("nothing was found"); + this._itemsFound = result; + this.setButtonStates(); } else { - this.updateMessage(result + " item(s) were found"); + this.moveNext(); } - - this._itemsFound = result; - this.setButtonStates(); } VisualSearchAndReplace.prototype.replaceText = function () { diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/visualsearchandreplace.aspx b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/visualsearchandreplace.aspx index aa79dee5d4..44d42fa7ef 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/visualsearchandreplace.aspx +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositesearchandreplace/visualsearchandreplace.aspx @@ -8,6 +8,33 @@ + + + @@ -21,56 +48,60 @@ label="${string:Composite.Web.VisualEditor:SearchAndReplace.LabelTitle}" image="${icon:composite}" height="auto" - resizable="false" - style="padding: 10px;" + resizable="false" binding="VisualSearchAndReplace"> - - + + - - - - + - + + + +
+
- + - + +
+
- + - + - - +
+
+
+
+ +
- + - - - +
\ No newline at end of file diff --git a/Website/Composite/localization/Composite.Web.SourceEditor.en-us.xml b/Website/Composite/localization/Composite.Web.SourceEditor.en-us.xml index 7c9015c3b7..55ee070738 100644 --- a/Website/Composite/localization/Composite.Web.SourceEditor.en-us.xml +++ b/Website/Composite/localization/Composite.Web.SourceEditor.en-us.xml @@ -29,6 +29,8 @@ + + diff --git a/Website/Composite/localization/Composite.Web.VisualEditor.en-us.xml b/Website/Composite/localization/Composite.Web.VisualEditor.en-us.xml index 5bc761d2b1..bcc9cf9205 100644 --- a/Website/Composite/localization/Composite.Web.VisualEditor.en-us.xml +++ b/Website/Composite/localization/Composite.Web.VisualEditor.en-us.xml @@ -186,12 +186,12 @@ - + - - - + + + From 4ecfc2f9c048b8d6e8f4d1fd520f5c46e8bd2cf0 Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 28 Aug 2018 17:33:33 +0200 Subject: [PATCH 13/51] Adding missing label for launch button tooltop. --- .../localization/Composite.Web.VisualEditor.en-us.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Website/Composite/localization/Composite.Web.VisualEditor.en-us.xml b/Website/Composite/localization/Composite.Web.VisualEditor.en-us.xml index bcc9cf9205..fd7ef38392 100644 --- a/Website/Composite/localization/Composite.Web.VisualEditor.en-us.xml +++ b/Website/Composite/localization/Composite.Web.VisualEditor.en-us.xml @@ -1,4 +1,4 @@ - + @@ -181,7 +181,8 @@ - + + From 17e3e24476c84817b7dfbe06cebe57279ab7ccda Mon Sep 17 00:00:00 2001 From: Peter Edwards Date: Wed, 29 Aug 2018 09:34:12 -0400 Subject: [PATCH 14/51] Fixing replace all enable state and cleaning up layout of source find and replace dialog --- ...ditorFindAndReplaceToolBarButtonBinding.js | 2 - .../codemirrorfindandreplace.aspx | 74 ++++++++++--------- .../codemirrorfindandreplace.js | 3 + 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorFindAndReplaceToolBarButtonBinding.js b/Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorFindAndReplaceToolBarButtonBinding.js index aa0facad73..0940b7e1d8 100644 --- a/Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorFindAndReplaceToolBarButtonBinding.js +++ b/Website/Composite/content/misc/editors/codemirroreditor/bindings/SourceEditorFindAndReplaceToolBarButtonBinding.js @@ -55,8 +55,6 @@ SourceEditorFindAndReplaceToolBarButtonBinding.prototype.onBindingAttach = funct */ SourceEditorFindAndReplaceToolBarButtonBinding.prototype.initializeSourceEditorComponent = function(binding, editor) { - debugger; - this._editorBinding = binding; this._codemirrorEditor = editor; diff --git a/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx b/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx index 04988f7dc1..494c5379b2 100644 --- a/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx +++ b/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.aspx @@ -37,42 +37,44 @@ - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-
- - - - - -
-
-
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + + +
+
+
+
diff --git a/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.js b/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.js index ab150513c7..77cc0cfa73 100644 --- a/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.js +++ b/Website/Composite/content/misc/editors/codemirroreditor/codemirrorfindandreplace.js @@ -66,6 +66,7 @@ CodemirrorFindAndReplace.prototype.setPageArgument = function (arg) { this._initialized = true; map.broadcasterReplace.setDisabled(true); + map.broadcasterReplaceAll.setDisabled(true); } CodemirrorFindAndReplace.prototype.getChecked = function (control) { @@ -144,12 +145,14 @@ CodemirrorFindAndReplace.prototype.findNextText = function () { this._editor.setSelection(this._cursor.from(), this._cursor.to()); map.broadcasterReplace.setDisabled(false); + map.broadcasterReplaceAll.setDisabled(false); this._textPosition = this._cursor.to() } else { this._textPosition = false; map.broadcasterReplace.setDisabled(true); + map.broadcasterReplaceAll.setDisabled(true); } } From 940ab498c910c4aed26fd0b6bd39f320b3d27154 Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 21 Sep 2018 16:36:31 +0200 Subject: [PATCH 15/51] elements with id now has id value used for filtering duplicated. Making two or more elements with the same ID will now only leave one. This is good, since you can overwide a value now, using id as key. --- .../Page/XElementToAspNetExtensions.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs b/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs index 2689a5be51..8616113a3a 100644 --- a/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs +++ b/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -312,7 +312,14 @@ internal static void MergeToHeadControl(this XhtmlDocument xhtmlDocument, HtmlHe var metaControl = new HtmlMeta(); foreach (var attribute in metaTag.Attributes()) { - metaControl.Attributes.Add(attribute.Name.LocalName, attribute.Value); + if (attribute.Name.LocalName == "id") + { + metaControl.ID = attribute.Value; + } + else + { + metaControl.Attributes.Add(attribute.Name.LocalName, attribute.Value); + } } headControl.Controls.AddAt(metaTagPosition++, metaControl); } @@ -346,6 +353,11 @@ private static void RemoveDuplicates(this HtmlHead headControl) { bool remove = IsDuplicate(uniqueIdValues, c.ClientID); + if (c.ClientID == "hello") + { + var x = 1; + } + if (c.Controls.Count == 0) { switch (c.TagName.ToLower()) From 576440ac3546f8c7115ccead36a88a7d042fc717 Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 21 Sep 2018 16:42:18 +0200 Subject: [PATCH 16/51] Removing (harmless) 'debug' code --- .../WebClient/Renderings/Page/XElementToAspNetExtensions.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs b/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs index 8616113a3a..ec038573c6 100644 --- a/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs +++ b/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs @@ -353,11 +353,6 @@ private static void RemoveDuplicates(this HtmlHead headControl) { bool remove = IsDuplicate(uniqueIdValues, c.ClientID); - if (c.ClientID == "hello") - { - var x = 1; - } - if (c.Controls.Count == 0) { switch (c.TagName.ToLower()) From 1cd53735bc104d415e52bacbfa0d215d802a9ea4 Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 3 Oct 2018 13:44:02 +0200 Subject: [PATCH 17/51] Foreign key 'null values' for Guid (0000-...0000) handling off for data validation during package install (empty guids refused). Fix was ensuring 'null values' gets typed correctly (for empty guids it was seen as a string) . Part of fix was also comparing values using .Equals() for package data installer. --- .../DataPackageFragmentInstaller.cs | 2 +- Composite/Data/ForeignPropertyInfo.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs index b23a922a01..250a49f9a1 100644 --- a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs +++ b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs @@ -598,7 +598,7 @@ private void ValidateNonDynamicAddedType(DataType dataType) object propertyValue = fieldValues[foreignKeyProperty.SourcePropertyName]; - if (propertyValue == null || propertyValue == foreignKeyProperty.NullReferenceValue) + if (propertyValue == null || propertyValue.Equals(foreignKeyProperty.NullReferenceValue)) { continue; } diff --git a/Composite/Data/ForeignPropertyInfo.cs b/Composite/Data/ForeignPropertyInfo.cs index 0013123fb7..f2d1fddba9 100644 --- a/Composite/Data/ForeignPropertyInfo.cs +++ b/Composite/Data/ForeignPropertyInfo.cs @@ -1,4 +1,5 @@ -using System; +using Composite.Core.Types; +using System; using System.Reflection; @@ -13,7 +14,7 @@ public sealed class ForeignPropertyInfo internal ForeignPropertyInfo(PropertyInfo sourcePropertyInfo, Type targetType, string targetKeyPropertyName, bool allowCascadeDeletes, object nullReferenceValue, Type nullReferenceValueType, bool isNullableString) : this(sourcePropertyInfo, targetType, targetKeyPropertyName, allowCascadeDeletes, isNullableString) { - this.NullReferenceValue = nullReferenceValue; + this.NullReferenceValue = ValueTypeConverter.Convert( nullReferenceValue, sourcePropertyInfo.PropertyType); this.NullReferenceValueType = nullReferenceValueType; this.IsNullReferenceValueSet = true; } From 546737d705a0b65170f6a5a84fa12bd74e486086 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 3 Oct 2018 13:50:27 +0200 Subject: [PATCH 18/51] Refactoring SetServerPageCacheDuration --- .../Response/SetServerPageCacheDuration.cs | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Web/Response/SetServerPageCacheDuration.cs b/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Web/Response/SetServerPageCacheDuration.cs index daa2a9c577..61cd838498 100644 --- a/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Web/Response/SetServerPageCacheDuration.cs +++ b/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Web/Response/SetServerPageCacheDuration.cs @@ -1,17 +1,16 @@ -using System; +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - -using Composite.Functions; using System.Web; +using Composite.Functions; using Composite.Plugins.Functions.FunctionProviders.StandardFunctionProvider.Foundation; -using Composite.Core.ResourceSystem; + namespace Composite.Plugins.Functions.FunctionProviders.StandardFunctionProvider.Web.Response { internal sealed class SetServerPageCacheDuration : StandardFunctionBase { + private const string ParameterName_MaxSeconds = "MaxSeconds"; + public SetServerPageCacheDuration(EntityTokenFactory entityTokenFactory) : base("SetServerPageCacheDuration", "Composite.Web.Response", typeof(void), entityTokenFactory) { @@ -21,10 +20,8 @@ protected override IEnumerable StandardFunctio { get { - WidgetFunctionProvider textboxWidget = StandardWidgetFunctions.TextBoxWidget; - yield return new StandardFunctionParameterProfile( - "MaxSeconds", typeof(int), true, new ConstantValueProvider(0), textboxWidget); + ParameterName_MaxSeconds, typeof(int), true, new ConstantValueProvider(0), StandardWidgetFunctions.TextBoxWidget); } } @@ -32,21 +29,22 @@ protected override IEnumerable StandardFunctio public override object Execute(ParameterList parameters, FunctionContextContainer context) { - if (HttpContext.Current != null && HttpContext.Current.Response != null) + var httpContext = HttpContext.Current; + if (httpContext?.Response == null) return null; + var cache = httpContext.Response.Cache; + + int maxSeconds = parameters.GetParameter(ParameterName_MaxSeconds); + + if (maxSeconds <= 0) + { + cache.SetCacheability(HttpCacheability.NoCache); + cache.SetNoServerCaching(); + cache.SetNoStore(); + } + else { - int maxSeconds = parameters.GetParameter("MaxSeconds"); - - if (maxSeconds < -1) - { - HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache); - HttpContext.Current.Response.Cache.SetNoServerCaching(); - HttpContext.Current.Response.Cache.SetNoStore(); - } - else - { - HttpContext.Current.Response.Cache.SetExpires(DateTime.Now.AddSeconds(maxSeconds)); - HttpContext.Current.Response.Cache.SetMaxAge(new TimeSpan(0, 0, maxSeconds)); - } + cache.SetExpires(DateTime.Now.AddSeconds(maxSeconds)); + cache.SetMaxAge(TimeSpan.FromSeconds(maxSeconds)); } return null; From f2fd0555c06a719df63bdaf559ea9bb667e2e06a Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 9 Oct 2018 11:03:56 +0200 Subject: [PATCH 19/51] Making DataEventSystemFacade.FireExternalStoreChangeEvent( ... ) public to better fix #623. --- Composite/Data/DataEventSystemFacade.cs | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Composite/Data/DataEventSystemFacade.cs b/Composite/Data/DataEventSystemFacade.cs index 30a428263c..a3d35aefd8 100644 --- a/Composite/Data/DataEventSystemFacade.cs +++ b/Composite/Data/DataEventSystemFacade.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using Composite.C1Console.Events; @@ -427,6 +427,18 @@ public static void UnsubscribeToDataAfterBuildNew(Type dataType, DataEventHandle } + /// + /// Fire this when an external store has changed outside the process to notify subscribers to the StoreChangeEvent. + /// + /// + /// + /// + public static void FireExternalStoreChangedEvent(Type dataType, PublicationScope publicationScope, CultureInfo locale) + { + FireStoreChangedEvent(dataType, publicationScope, locale, false); + } + + internal static void FireDataBeforeAddEvent(Type dataType, IData data) { var args = new DataEventArgs(dataType, data); @@ -453,18 +465,6 @@ internal static void FireDataAfterAddEvent(Type dataType, IData data) } - /// - /// Fire this when an external store has changed outside the process to notify subscribers to the StoreChangeEvent. - /// - /// - /// - /// - internal static void FireExternalStoreChangedEvent(Type dataType, PublicationScope publicationScope, CultureInfo locale) - { - FireStoreChangedEvent(dataType, publicationScope, locale, false); - } - - /// /// Follow up event for intally fired events /// From c9c7680dc4fe79aebf1790dfab289bd394b366cc Mon Sep 17 00:00:00 2001 From: Alnur Ismail Date: Tue, 9 Oct 2018 11:01:40 -0400 Subject: [PATCH 20/51] Adding and updating path encoding logic. --- Composite/Core/UrlBuilder.cs | 8 ++++++++ Composite/Plugins/Routing/Pages/DefaultPageUrlProvider.cs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Composite/Core/UrlBuilder.cs b/Composite/Core/UrlBuilder.cs index 3ffda34a3f..1c1f87ad6b 100644 --- a/Composite/Core/UrlBuilder.cs +++ b/Composite/Core/UrlBuilder.cs @@ -92,6 +92,14 @@ public static string UrlEncode(string urlPart) } } + public static string UrlPathEncode(string urlPart) + { + using (new NoHttpContext()) + { + return HttpUtility.UrlPathEncode(urlPart); + } + } + public static string UrlDecode(string urlPart) { using (new NoHttpContext()) diff --git a/Composite/Plugins/Routing/Pages/DefaultPageUrlProvider.cs b/Composite/Plugins/Routing/Pages/DefaultPageUrlProvider.cs index 3a0ec40b3a..654ef4dfc4 100644 --- a/Composite/Plugins/Routing/Pages/DefaultPageUrlProvider.cs +++ b/Composite/Plugins/Routing/Pages/DefaultPageUrlProvider.cs @@ -883,7 +883,7 @@ private static StringBuilder AppendPathInfo(StringBuilder sb, string pathInfo) sb.Append('/'); } - sb.Append(UrlBuilder.DefaultHttpEncoder.UrlEncode(pathInfoPart)); + sb.Append(UrlBuilder.DefaultHttpEncoder.UrlPathEncode(pathInfoPart)); isFirst = false; } From b255eab3524f675067dead618af2c5533d002bc5 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Fri, 12 Oct 2018 15:58:41 +0300 Subject: [PATCH 21/51] fix url-polyfill dependency (#626) --- Website/jspm.config.js | 18 ++++++------------ Website/package.json | 8 ++------ 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/Website/jspm.config.js b/Website/jspm.config.js index b4b8603e6c..56f3304c3d 100644 --- a/Website/jspm.config.js +++ b/Website/jspm.config.js @@ -287,7 +287,6 @@ SystemJS.config({ map: { "fixed-data-table-2": "npm:fixed-data-table-2@0.7.6", "immutable": "npm:immutable@3.8.1", - "github/url-polyfill": "github:github/url-polyfill@0.5.6", "bluebird": "npm:bluebird@3.4.6", "module": "npm:jspm-nodelibs-module@0.2.0", "plugin-babel": "npm:systemjs-plugin-babel@0.0.13", @@ -332,7 +331,7 @@ SystemJS.config({ "redux-thunk": "npm:redux-thunk@2.1.0", "svg": "github:npbenjohnson/plugin-svg@0.1.0", "url": "github:jspm/nodelibs-url@0.2.0-alpha", - "url-polyfill": "github:github/url-polyfill@0.5.6", + "url-polyfill": "npm:url-polyfill@1.0.13", "util": "github:jspm/nodelibs-util@0.2.0-alpha", "vm": "npm:jspm-nodelibs-vm@0.2.0", "wampy": "npm:wampy@4.0.0", @@ -834,11 +833,6 @@ SystemJS.config({ "domain-browserify": "npm:domain-browser@1.1.7" } }, - "npm:jspm-nodelibs-crypto@0.2.0": { - "map": { - "crypto-browserify": "npm:crypto-browserify@3.11.0" - } - }, "npm:jspm-nodelibs-zlib@0.2.0": { "map": { "zlib-browserify": "npm:browserify-zlib@0.1.4" @@ -849,11 +843,6 @@ SystemJS.config({ "string_decoder-browserify": "npm:string_decoder@0.10.31" } }, - "npm:jspm-nodelibs-os@0.2.0": { - "map": { - "os-browserify": "npm:os-browserify@0.2.1" - } - }, "github:jspm/nodelibs-http@0.2.0-alpha": { "map": { "http-browserify": "npm:stream-http@2.5.0" @@ -873,6 +862,11 @@ SystemJS.config({ "map": { "url-browserify": "npm:url@0.11.0" } + }, + "github:jspm/nodelibs-crypto@0.2.0-alpha": { + "map": { + "crypto-browserify": "npm:crypto-browserify@3.11.0" + } } } }); diff --git a/Website/package.json b/Website/package.json index 27c2085508..9018cceffd 100644 --- a/Website/package.json +++ b/Website/package.json @@ -14,7 +14,7 @@ "devDependencies": { "JSONPath": "^0.10.0", "autoprefixer-core": "^5.2.1", - "chromedriver": "^2.34.0", + "chromedriver": "^2.34.0", "chokidar-socket-emitter": "0.5.4", "csswring": "^3.0.5", "eslint": "3.1.1", @@ -48,7 +48,6 @@ "main": "Composite/console/console.js", "dependencies": { "fixed-data-table-2": "npm:fixed-data-table-2@^0.7.6", - "github/url-polyfill": "github:github/url-polyfill@^0.5.6", "normalizr": "npm:normalizr@^2.2.1", "plugin-babel": "npm:systemjs-plugin-babel@^0.0.13", "rc-table": "npm:rc-table@^5.0.3", @@ -62,7 +61,7 @@ "styled-components": "npm:styled-components@^1.1.1", "svg": "github:npbenjohnson/plugin-svg@^0.1.0", "text": "github:systemjs/plugin-text@^0.0.9", - "url-polyfill": "github:github/url-polyfill@^0.5.6", + "url-polyfill": "npm:url-polyfill@1.0.13", "wampy": "npm:wampy@^4.0.0", "whatwg-fetch": "npm:whatwg-fetch@^1.0.0" }, @@ -128,9 +127,6 @@ "zlib": "npm:jspm-nodelibs-zlib@^0.2.0" }, "overrides": { - "github:github/url-polyfill@0.5.6": { - "main": "url.js" - }, "github:socketio/socket.io-client@1.5.0": { "main": "socket.io.js" }, From 67c07f24521350f53ff1d6f6b960a84876dcc200 Mon Sep 17 00:00:00 2001 From: Alnur Ismail Date: Mon, 15 Oct 2018 11:05:25 -0400 Subject: [PATCH 22/51] Update Website/Composite/services/Media/ImageManipulator.ashx Adds support for streams that don't support returning a length (i.e. cloud streams). --- Website/Composite/services/Media/ImageManipulator.ashx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Website/Composite/services/Media/ImageManipulator.ashx b/Website/Composite/services/Media/ImageManipulator.ashx index c5bf69630e..758d5c2a62 100644 --- a/Website/Composite/services/Media/ImageManipulator.ashx +++ b/Website/Composite/services/Media/ImageManipulator.ashx @@ -331,7 +331,13 @@ public class ImageManipulator : IHttpHandler { using (Stream writeStream = mediaFile.GetNewWriteStream()) { - image.Save(writeStream, GetImageFormat(mediaFile.MimeType)); + // Use a temporary memory stream when the writeStream doesn't support returning the stream length which is called when using Image.Save() + using (var ms = new MemoryStream()) + { + image.Save(ms, GetImageFormat(mediaFile.MimeType)); + var buffer = ms.GetBuffer(); + writeStream.Write(buffer, 0, buffer.Length); + } } DataFacade.Update(mediaFile); } From b7f598dc63b8804409dff350ebf1f6f8c7a17cf1 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 17 Oct 2018 16:39:56 +0200 Subject: [PATCH 23/51] Refactoring and better exceptions --- .../Foundation/TreePerspectiveEntityToken.cs | 49 ++-------------- .../WebClient/Setup/SetupServiceFacade.cs | 10 +++- Composite/Data/PageMetaDataFacade.cs | 52 ++++++----------- .../SqlDataTypeStoresContainer.cs | 56 ++++++------------- 4 files changed, 50 insertions(+), 117 deletions(-) diff --git a/Composite/C1Console/Trees/Foundation/TreePerspectiveEntityToken.cs b/Composite/C1Console/Trees/Foundation/TreePerspectiveEntityToken.cs index e24967b701..1266f46666 100644 --- a/Composite/C1Console/Trees/Foundation/TreePerspectiveEntityToken.cs +++ b/Composite/C1Console/Trees/Foundation/TreePerspectiveEntityToken.cs @@ -1,7 +1,5 @@ -using Composite.C1Console.Security; -using System.Collections.Generic; +using Composite.C1Console.Security; using Composite.Core.Serialization; -using System.Text; using Composite.Core; using Newtonsoft.Json; @@ -12,65 +10,30 @@ namespace Composite.C1Console.Trees.Foundation [SecurityAncestorProvider(typeof(Composite.C1Console.Security.SecurityAncestorProviders.NoAncestorSecurityAncestorProvider))] public class TreePerspectiveEntityToken : EntityToken { - private readonly string _id; - private List _childTrees = new List(); - - /// [JsonConstructor] public TreePerspectiveEntityToken(string id) { - _id = id; + Id = id; } /// - public override string Id - { - get { return _id; } - } + public override string Id { get; } /// [JsonIgnore] - public override string Type - { - get { return "C1Trees"; } - } + public override string Type => "C1Trees"; /// [JsonIgnore] - public override string Source - { - get { return "C1Trees"; } - } - - - /// - public void AddChildTree(string treeId) - { - _childTrees.Add(treeId); - } + public override string Source => "C1Trees"; /// - public override string Serialize() - { - return CompositeJsonSerializer.Serialize(this); - /*StringBuilder sb = new StringBuilder(); - - StringConversionServices.SerializeKeyValuePair(sb, "Id", Id); - - int counter = 0; - foreach (string treeId in _childTrees) - { - string key = "TreeId" + (counter++); - StringConversionServices.SerializeKeyValuePair(sb, key, treeId); - } - - return sb.ToString();*/ - } + public override string Serialize() => CompositeJsonSerializer.Serialize(this); /// diff --git a/Composite/Core/WebClient/Setup/SetupServiceFacade.cs b/Composite/Core/WebClient/Setup/SetupServiceFacade.cs index 305feed704..b3fa9de824 100644 --- a/Composite/Core/WebClient/Setup/SetupServiceFacade.cs +++ b/Composite/Core/WebClient/Setup/SetupServiceFacade.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -350,7 +350,13 @@ private static SetupSoapClient CreateClient() private static IEnumerable GetPackageUrls(XElement setupDescription) { - int maxkey = setupDescription.Descendants().Attributes(KeyAttributeName).Select(f => (int)f).Max(); + var keyAttributes = setupDescription.Descendants().Attributes(KeyAttributeName).ToList(); + if (!keyAttributes.Any()) + { + throw new InvalidOperationException("Invalid setup description: " + setupDescription); + } + + int maxkey = keyAttributes.Select(f => (int)f).Max(); SetupSoapClient client = CreateClient(); diff --git a/Composite/Data/PageMetaDataFacade.cs b/Composite/Data/PageMetaDataFacade.cs index 1c0bf5d872..80b5cb14cf 100644 --- a/Composite/Data/PageMetaDataFacade.cs +++ b/Composite/Data/PageMetaDataFacade.cs @@ -57,7 +57,7 @@ public static IEnumerable GetDefinedMetaDataTypes(this IPage page) foreach (Guid metaDataTypeId in metaDataTypeIds) { - DataTypeDescriptor dataTypeDescriptor = DynamicTypeManager.GetDataTypeDescriptor(metaDataTypeId); + var dataTypeDescriptor = DynamicTypeManager.GetDataTypeDescriptor(metaDataTypeId); yield return TypeManager.GetType(dataTypeDescriptor.TypeManagerTypeName); } @@ -78,7 +78,7 @@ public static IEnumerable> GetDefinedMetaDataTypeAndNames(th foreach (Tuple metaDataTypeIdAndName in metaDataTypeIdAndNames) { - DataTypeDescriptor dataTypeDescriptor = DynamicTypeManager.GetDataTypeDescriptor(metaDataTypeIdAndName.Item1); + var dataTypeDescriptor = DynamicTypeManager.GetDataTypeDescriptor(metaDataTypeIdAndName.Item1); yield return new Tuple(TypeManager.GetType(dataTypeDescriptor.TypeManagerTypeName), metaDataTypeIdAndName.Item2); } @@ -130,7 +130,7 @@ public static List> GetAllMetaDataContainers() { bool anyExists = DataFacade.GetData().Any(); - if (anyExists == false) + if (!anyExists) { ICompositionContainer defaultContainer = DataFacade.BuildNew(); @@ -183,7 +183,7 @@ public static IEnumerable GetAllowedMetaDataTypes(this IPage page) { foreach (Guid metaDataTypeId in GetAllowedMetaDataDefinitions(page).Select(f => f.MetaDataTypeId).Distinct()) { - DataTypeDescriptor dataTypeDescriptor = DynamicTypeManager.GetDataTypeDescriptor(metaDataTypeId); + var dataTypeDescriptor = DynamicTypeManager.GetDataTypeDescriptor(metaDataTypeId); yield return TypeManager.GetType(dataTypeDescriptor.TypeManagerTypeName); } @@ -441,15 +441,7 @@ public static IEnumerable GetMetaDataAffectedPages(this IPage definingPag for (int i = 0; i < startLevel; i++) { - List newPages = new List(); - - foreach (IPage p in pages) - { - IEnumerable children = p.GetChildren(); - newPages.AddRange(children); - } - - pages = newPages; + pages = pages.SelectMany(page => page.GetChildren()).ToList(); } @@ -462,15 +454,7 @@ public static IEnumerable GetMetaDataAffectedPages(this IPage definingPag yield return p; } - List newPages = new List(); - - foreach (IPage p in pages) - { - IEnumerable children = p.GetChildren(); - newPages.AddRange(children); - } - - pages = newPages; + pages = pages.SelectMany(page => page.GetChildren()).ToList(); } } @@ -504,7 +488,7 @@ public static bool IsDefinitionAllowed(Guid definingItemId, string name, string if (pageMetaDataDefinition.DefiningItemId == definingItemId && pageMetaDataDefinition.MetaDataTypeId == metaDataTypeId && pageMetaDataDefinition.Label != label && - pageMetaDataDefinitions.Count() == 1) + pageMetaDataDefinitions.Count == 1) { return true; // Allow renaming of label } @@ -564,6 +548,8 @@ public static void AddMetaDataDefinition(this IPage definingPage, string name, s /// public static void AddMetaDataDefinition(this IPageType definingPageType, string name, string label, Guid metaDataTypeId, Guid metaDataContainerId) { + Verify.ArgumentNotNull(definingPageType, nameof(definingPageType)); + AddDefinition(definingPageType.Id, name, label, metaDataTypeId, metaDataContainerId, 0, 0); } @@ -627,14 +613,13 @@ public static void AddNewMetaDataToExistingPage(this IPage page, string metaData IData data = page.GetMetaData(metaDataDefinitionName, metaDataType); if (data != null) return; - IPublishControlled newData = DataFacade.BuildNew(metaDataType) as IPublishControlled; + var newData = (IPublishControlled) DataFacade.BuildNew(metaDataType); newDataTemplate.FullCopyChangedTo(newData); newData.PublicationStatus = GenericPublishProcessController.Draft; - PageMetaDataFacade.AssignMetaDataSpecificValues(newData, metaDataDefinitionName, page); + AssignMetaDataSpecificValues(newData, metaDataDefinitionName, page); - ILocalizedControlled localizedData = newData as ILocalizedControlled; - if (localizedData != null) + if (newData is ILocalizedControlled localizedData) { localizedData.SourceCultureName = page.SourceCultureName; } @@ -660,7 +645,7 @@ public static void AddNewMetaDataToExistingPages(this IPageType definingPageType { IPageMetaDataDefinition pageMetaDataDefinition = GetMetaDataDefinition(definingPageType.Id, metaDataDefinitionName); - DataTypeDescriptor dataTypeDescriptor = DynamicTypeManager.GetDataTypeDescriptor(pageMetaDataDefinition.MetaDataTypeId); + var dataTypeDescriptor = DynamicTypeManager.GetDataTypeDescriptor(pageMetaDataDefinition.MetaDataTypeId); Type metaDataType = TypeManager.GetType(dataTypeDescriptor.TypeManagerTypeName); IEnumerable affectedPages = PageMetaDataFacade.GetMetaDataAffectedPagesByPageTypeId(definingPageType.Id); @@ -677,18 +662,17 @@ private static void AddNewMetaDataToExistingPages(IEnumerable affectedPag IData data = affectedPage.GetMetaData(metaDataDefinitionName, metaDataType); if (data != null) continue; - IPublishControlled newData = DataFacade.BuildNew(metaDataType) as IPublishControlled; + var newData = (IPublishControlled) DataFacade.BuildNew(metaDataType); newDataTemplate.FullCopyChangedTo(newData); newData.PublicationStatus = GenericPublishProcessController.Draft; PageMetaDataFacade.AssignMetaDataSpecificValues(newData, metaDataDefinitionName, affectedPage); - ILocalizedControlled localizedData = newData as ILocalizedControlled; - if(localizedData != null) + if(newData is ILocalizedControlled localizedData) { localizedData.SourceCultureName = UserSettings.ActiveLocaleCultureInfo.Name; } - newData = DataFacade.AddNew((IData) newData) as IPublishControlled; + newData = (IPublishControlled) DataFacade.AddNew((IData) newData); if (newData.PublicationStatus != affectedPage.PublicationStatus) { @@ -854,8 +838,8 @@ private static void RemoveDefinitionDeleteData(string definitionName, Type metaD continue; } - bool existsINOtherScope = ExistInOtherScope(page, otherPageMetaDataDefinitions); - if (existsINOtherScope) + bool existsInOtherScope = ExistInOtherScope(page, otherPageMetaDataDefinitions); + if (existsInOtherScope) { datasNotToDelete.Add(data); } diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/SqlDataTypeStoresContainer.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/SqlDataTypeStoresContainer.cs index 746d418bc9..2f85110b17 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/SqlDataTypeStoresContainer.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/SqlDataTypeStoresContainer.cs @@ -5,8 +5,6 @@ using System.Data.Linq; using System.Data.SqlTypes; using System.Reflection; -using Composite.Core; -using Composite.Core.Extensions; using Composite.Core.Sql; using Composite.Core.Threading; using Composite.Data; @@ -20,7 +18,6 @@ namespace Composite.Plugins.Data.DataProviders.MSSqlServerDataProvider internal sealed class SqlDataTypeStoresContainer { private static readonly ConcurrentDictionary> _dateTimeProperties = new ConcurrentDictionary>(); - private static readonly object[] EmptyObjectsArray = new object[0]; private readonly Dictionary _sqlDataTypeStores = new Dictionary(); @@ -49,22 +46,19 @@ internal SqlDataTypeStoresContainer(string providerName, string connectionString /// /// All working data types /// - public IEnumerable SupportedInterfaces { get { return _supportedInterfaces; } } - + public IEnumerable SupportedInterfaces => _supportedInterfaces; /// /// All data types, including non working due to config error or something else /// - public IEnumerable KnownInterfaces { get { return _knownInterfaces; } } - + public IEnumerable KnownInterfaces => _knownInterfaces; /// /// All working generated data types /// - public IEnumerable GeneratedInterfaces { get { return _generatedInterfaces; } } - + public IEnumerable GeneratedInterfaces => _generatedInterfaces; internal Type DataContextClass { get; set; } @@ -76,7 +70,7 @@ public SqlDataTypeStore GetDataTypeStore(Type interfaceType) SqlDataTypeStore store; if (!_sqlDataTypeStores.TryGetValue(interfaceType, out store)) { - throw new InvalidOperationException("Interface '{0}' is not supported".FormatWith(interfaceType)); + throw new InvalidOperationException($"Interface '{interfaceType}' is not supported"); } return store; @@ -91,6 +85,7 @@ public SqlDataTypeStore GetDataTypeStore(Type interfaceType) /// internal void AddSupportedDataTypeStore(Type interfaceType, SqlDataTypeStore sqlDataTypeStore) { + Verify.That(!_sqlDataTypeStores.ContainsKey(interfaceType), "Type {0} is registered in the SqlDataProvider configuration multiple types", interfaceType); _sqlDataTypeStores.Add(interfaceType, sqlDataTypeStore); _supportedInterfaces.Add(interfaceType); @@ -123,7 +118,7 @@ public List AddNew(IEnumerable dataset, DataProviderContext dataProvide where T : class, IData { SqlDataTypeStore sqlDataTypeStore = TryGetsqlDataTypeStore(typeof(T)); - if (sqlDataTypeStore == null) throw new InvalidOperationException(string.Format("The interface '{0}' has not been configures", typeof(T).FullName)); + if (sqlDataTypeStore == null) throw new InvalidOperationException($"The interface '{typeof(T).FullName}' has not been configured"); var resultDataset = new List(); @@ -131,11 +126,11 @@ public List AddNew(IEnumerable dataset, DataProviderContext dataProvide { foreach (IData data in dataset) { - Verify.ArgumentCondition(data != null, "dataset", "Data set may not contain nulls"); + Verify.ArgumentCondition(data != null, "dataset", "Data set may not contain null values"); IData newData = sqlDataTypeStore.AddNew(data, dataProviderContext, dataContext); - (newData as IEntity).Commit(); + ((IEntity) newData).Commit(); CheckConstraints(newData); @@ -179,12 +174,12 @@ public void Delete(IEnumerable dataSourceIds, DataProviderContext { foreach (DataSourceId dataSourceId in dataSourceIds) { - if (dataSourceId == null) throw new ArgumentException("dataSourceIds contains nulls"); + if (dataSourceId == null) throw new ArgumentException("dataSourceIds contains null values"); using (new DataScope(dataSourceId.DataScopeIdentifier, dataSourceId.LocaleScope)) { SqlDataTypeStore sqlDataTypeStore = TryGetsqlDataTypeStore(dataSourceId.InterfaceType); - if (sqlDataTypeStore == null) throw new InvalidOperationException(string.Format("The interface '{0}' has not been configures", dataSourceId.InterfaceType.FullName)); + if (sqlDataTypeStore == null) throw new InvalidOperationException($"The interface '{dataSourceId.InterfaceType.FullName}' has not been configured"); IData data = sqlDataTypeStore.GetDataByDataId(dataSourceId.DataId, dataProivderContext); @@ -203,10 +198,7 @@ public void Delete(IEnumerable dataSourceIds, DataProviderContext } finally { - if (dataContext != null) - { - dataContext.Dispose(); - } + dataContext?.Dispose(); } } @@ -233,7 +225,7 @@ private static ITable GetTable(DataContext dataContext, Object entity) Type entityType = entity.GetType(); ITable table = dataContext.GetTable(entityType); - Verify.IsNotNull(table, "Failed to find a table, related to '{0}' type".FormatWith(entityType.FullName)); + Verify.IsNotNull(table, "Failed to find a table, related to '{0}' type", entityType.FullName); return table; } @@ -242,24 +234,21 @@ private static ITable GetTable(DataContext dataContext, Object entity) private static void CheckConstraints(IData data) { // DateTime.MinValue is not supported by SQL, since it has a different minimal value for a date - if (data is IChangeHistory) + if (data is IChangeHistory changeHistory + && changeHistory.ChangeDate == DateTime.MinValue) { - var changeHistory = (IChangeHistory)data; - if (changeHistory.ChangeDate == DateTime.MinValue) - { - changeHistory.ChangeDate = DateTime.Now; - } + changeHistory.ChangeDate = DateTime.Now; } foreach(PropertyInfo dateTimeProperty in GetDateTimeProperties(data.DataSourceId.InterfaceType)) { - object value = dateTimeProperty.GetValue(data, EmptyObjectsArray); + object value = dateTimeProperty.GetValue(data, null); if(value == null) continue; DateTime dateTime = (DateTime) value; if(dateTime == DateTime.MinValue) { - dateTimeProperty.SetValue(data, SqlDateTime.MinValue.Value, EmptyObjectsArray); + dateTimeProperty.SetValue(data, SqlDateTime.MinValue.Value, null); } } } @@ -353,16 +342,7 @@ private DataContext CreateDataContext() internal static void SubmitChanges(DataContext dataContext) { - try - { - dataContext.SubmitChanges(); - } - catch (Exception ex) - { - Log.LogWarning("SqlDataProviderCodeGeneratorResult", ex); - - throw; - } + dataContext.SubmitChanges(); } From d2ffe2300b3083930fc3d8761fdc6dd459596867 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 17 Oct 2018 17:16:00 +0200 Subject: [PATCH 24/51] Fixing merge induced compilation error --- Composite/Core/WebClient/ApplicationLevelEventHandlers.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs index fe001a1618..805423a630 100644 --- a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs +++ b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs @@ -1,14 +1,14 @@ -using System; +using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; using System.Text; using System.Web; +using Composite.AspNet; using Composite.C1Console.Actions.Data; using Composite.C1Console.Elements; using Composite.C1Console.Events; using Composite.Search; -using Composite.Search.DocumentSources; using Composite.Core.Application; using Composite.Core.Configuration; using Composite.Core.Extensions; @@ -18,8 +18,6 @@ using Composite.Core.Routing; using Composite.Core.Threading; using Composite.Core.Types; -using Composite.Core.WebClient.Services.WampRouter; -using Composite.Data; using Composite.Data.Types; using Composite.Functions; using Composite.Plugins.Elements.UrlToEntityToken; From b568aa760b9c46390c0e6075aab50ab4c9e7a5bf Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 17 Oct 2018 17:51:41 +0200 Subject: [PATCH 25/51] Tree Definitions - and support new attribute "BrowserImage" (a local URL) - setting this will make the C1 Console use the specified image instead of the icon for list / detail views. If BrowserUrl is also used, BrowserUrl will be used for detail views and BrowserImage for list views. Fix #606. --- .../C1Console/Trees/DataElementsTreeNode.cs | 27 +++++++++++++++++++ .../Foundation/TreeNodeCreatorFactory.cs | 8 ++++-- .../C1Console/Trees/SimpleElementTreeNode.cs | 19 +++++++++++++ Website/Composite/schemas/Trees/Tree.xsd | 14 ++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/Composite/C1Console/Trees/DataElementsTreeNode.cs b/Composite/C1Console/Trees/DataElementsTreeNode.cs index d19bd4d37b..5f24c4ae59 100644 --- a/Composite/C1Console/Trees/DataElementsTreeNode.cs +++ b/Composite/C1Console/Trees/DataElementsTreeNode.cs @@ -50,6 +50,8 @@ public class DataElementsTreeNode : DataFilteringTreeNode /// public string BrowserUrl { get; internal set; } // Optional + /// + public string BrowserImage { get; internal set; } // Optional // Cached values private Dictionary ParentFilteringHelpers { get; set; } @@ -65,6 +67,7 @@ public class DataElementsTreeNode : DataFilteringTreeNode private DynamicValuesHelper LabelDynamicValuesHelper { get; set; } private DynamicValuesHelper ToolTipDynamicValuesHelper { get; set; } private DynamicValuesHelper BrowserUrlDynamicValuesHelper { get; set; } + private DynamicValuesHelper BrowserImageDynamicValuesHelper { get; set; } private static readonly ResourceHandle LocalizeDataTypeIcon = ResourceHandle.BuildIconFromDefaultProvider("tree-localize-data"); @@ -371,6 +374,24 @@ EntityToken parentEntityToken } + if (this.BrowserImage != null) + { + var url = this.BrowserImageDynamicValuesHelper.ReplaceValues(replaceContext); + + if (!url.Contains("//")) + { + url = Core.WebClient.UrlUtils.ResolvePublicUrl(url); + } + + element.PropertyBag.Add("ListViewImage", url); + + if (this.BrowserUrl == null) + { + element.PropertyBag.Add("DetailViewImage", url); + } + } + + if (itemLocalizationEnabledAndForeign) { var actionToken = new WorkflowActionToken( @@ -484,6 +505,12 @@ protected override void OnInitialize() this.BrowserUrlDynamicValuesHelper = new DynamicValuesHelper(this.BrowserUrl); this.BrowserUrlDynamicValuesHelper.Initialize(this); } + + if (this.BrowserImage != null) + { + this.BrowserImageDynamicValuesHelper = new DynamicValuesHelper(this.BrowserImage); + this.BrowserImageDynamicValuesHelper.Initialize(this); + } } diff --git a/Composite/C1Console/Trees/Foundation/TreeNodeCreatorFactory.cs b/Composite/C1Console/Trees/Foundation/TreeNodeCreatorFactory.cs index d9bd9c5c85..b3f64d9252 100644 --- a/Composite/C1Console/Trees/Foundation/TreeNodeCreatorFactory.cs +++ b/Composite/C1Console/Trees/Foundation/TreeNodeCreatorFactory.cs @@ -135,6 +135,7 @@ private static TreeNode BuildDataElementsTreeNode(XElement element, Tree tree) XAttribute showForeignItemsAttribute = element.Attribute("ShowForeignItems"); XAttribute leafDisplayAttribute = element.Attribute("Display"); XAttribute browserUrlAttribute = element.Attribute("BrowserUrl"); + XAttribute browserImagelAttribute = element.Attribute("BrowserImage"); if (typeAttribute == null) { @@ -172,7 +173,8 @@ private static TreeNode BuildDataElementsTreeNode(XElement element, Tree tree) OpenedIcon = openedIcon, ShowForeignItems = showForeignItemsAttribute.GetValueOrDefault("true").ToLowerInvariant() == "true", Display = leafDisplay, - BrowserUrl = browserUrlAttribute.GetValueOrDefault(null) + BrowserUrl = browserUrlAttribute.GetValueOrDefault(null), + BrowserImage = browserImagelAttribute.GetValueOrDefault(null), }; List treeNodes; @@ -232,6 +234,7 @@ private static TreeNode BuildSimpleElementTreeNode(XElement element, Tree tree) XAttribute iconAttribute = element.Attribute("Icon"); XAttribute openedIconAttribute = element.Attribute("OpenedIcon"); XAttribute browserUrlAttribute = element.Attribute("BrowserUrl"); + XAttribute browserImageAttribute = element.Attribute("BrowserImage"); if (idAttribute == null) { @@ -275,7 +278,8 @@ private static TreeNode BuildSimpleElementTreeNode(XElement element, Tree tree) ToolTip = toolTipAttribute.GetValueOrDefault(labelAttribute.Value), Icon = icon, OpenIcon = openedIcon, - BrowserUrl = browserUrlAttribute.GetValueOrDefault(null) + BrowserUrl = browserUrlAttribute.GetValueOrDefault(null), + BrowserImage = browserImageAttribute.GetValueOrDefault(null) }; } diff --git a/Composite/C1Console/Trees/SimpleElementTreeNode.cs b/Composite/C1Console/Trees/SimpleElementTreeNode.cs index 76472eb688..c08da6968a 100644 --- a/Composite/C1Console/Trees/SimpleElementTreeNode.cs +++ b/Composite/C1Console/Trees/SimpleElementTreeNode.cs @@ -16,6 +16,7 @@ internal class SimpleElementTreeNode : TreeNode public ResourceHandle Icon { get; internal set; } // Defaults to 'folder' public ResourceHandle OpenIcon { get; internal set; } // Defaults to 'open-folder' or if Icon is set, then, Icon public string BrowserUrl { get; internal set; } // Defaults to no URL, what will be shows in console browser on focus + public string BrowserImage { get; internal set; } // Defaults to no (image) URL, what will be shows in console browser on focus / lists // Cached values internal DynamicValuesHelper LabelDynamicValuesHelper { get; set; } @@ -95,6 +96,24 @@ protected override IEnumerable OnGetElements(EntityToken parentEntityTo } + if (this.BrowserImage != null) + { + var url = this.BrowserImage; + + if (!url.Contains("//")) + { + url = Core.WebClient.UrlUtils.ResolvePublicUrl(url); + } + + element.PropertyBag.Add("ListViewImage", url); + + if (this.BrowserUrl == null) + { + element.PropertyBag.Add("DetailViewImage", url); + } + } + + if (parentEntityToken is TreePerspectiveEntityToken) { element.ElementHandle.Piggyback[StringConstants.PiggybagTreeId] = Tree.TreeId; diff --git a/Website/Composite/schemas/Trees/Tree.xsd b/Website/Composite/schemas/Trees/Tree.xsd index 816bb33651..afb6fe30ee 100644 --- a/Website/Composite/schemas/Trees/Tree.xsd +++ b/Website/Composite/schemas/Trees/Tree.xsd @@ -823,6 +823,12 @@ + + + Custom image (URL) to display in the console browser when this element is shown in lists / focused. + + + @@ -955,6 +961,14 @@ + + + + Custom image (URL) for each data item, to display in the console browser when this element is focused. Use ${C1:Data:[TypeName]:[FieldName]} to get a field value of a parent (or self) data element. + + + + From 0390515ba0378e2c79255a82db3f4af45f288a09 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 18 Oct 2018 10:20:48 +0200 Subject: [PATCH 26/51] Page Menu titles can now exceed 64 characters (new max 192), fix #604 --- .../PageElementProvider/EditPageWorkflow.cs | 6 +++--- Composite/Data/Types/IPage.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs b/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs index 2a79d34d6c..f88032783d 100644 --- a/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs +++ b/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Web.UI; @@ -577,8 +577,8 @@ private void ValidateSave(object sender, ConditionalEventArgs e) TrimFieldValues(selectedPage); if (!FieldHasValidLength(selectedPage.Title, nameof(IPage.Title), 255) - || !FieldHasValidLength(selectedPage.MenuTitle, nameof(IPage.MenuTitle), 64) - || !FieldHasValidLength(selectedPage.UrlTitle, nameof(IPage.UrlTitle), 64) + || !FieldHasValidLength(selectedPage.MenuTitle, nameof(IPage.MenuTitle), 192) + || !FieldHasValidLength(selectedPage.UrlTitle, nameof(IPage.UrlTitle), 192) || !FieldHasValidLength(selectedPage.FriendlyUrl, nameof(IPage.FriendlyUrl), 64)) { e.Result = false; diff --git a/Composite/Data/Types/IPage.cs b/Composite/Data/Types/IPage.cs index 9a96461578..214ec69d38 100644 --- a/Composite/Data/Types/IPage.cs +++ b/Composite/Data/Types/IPage.cs @@ -1,4 +1,4 @@ -using System; +using System; using Composite.Core.WebClient.Renderings.Data; using Composite.Data.Hierarchy; using Composite.Data.Hierarchy.DataAncestorProviders; @@ -59,7 +59,7 @@ public interface IPage : IData, IChangeHistory, ICreationHistory, IPublishContro /// - [StoreFieldType(PhysicalStoreFieldType.String, 64, IsNullable = true)] + [StoreFieldType(PhysicalStoreFieldType.String, 192, IsNullable = true)] [ImmutableFieldId("{3E398FA5-7961-4a75-A6CE-C147B7F4B90A}")] [SearchableField(true, false, false)] string MenuTitle { get; set; } From 28322b5e6be240dfcffa6626fc717278dd8953d8 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 18 Oct 2018 10:51:37 +0200 Subject: [PATCH 27/51] SiteMap provider - adding code comments and refactoring --- Composite/AspNet/CmsPageSiteMapProvider.cs | 29 ++++--------------- Composite/AspNet/ICmsSiteMapNode.cs | 5 ++-- Composite/AspNet/ICmsSiteMapProvider.cs | 8 +++-- Composite/AspNet/ISchemaOrgSiteMapNode.cs | 12 ++++---- Composite/AspNet/ISiteMapPlugin.cs | 12 ++++---- .../ApplicationLevelEventHandlers.cs | 27 ++++++++--------- 6 files changed, 40 insertions(+), 53 deletions(-) diff --git a/Composite/AspNet/CmsPageSiteMapProvider.cs b/Composite/AspNet/CmsPageSiteMapProvider.cs index a2e4e97e7a..b3566acdf7 100644 --- a/Composite/AspNet/CmsPageSiteMapProvider.cs +++ b/Composite/AspNet/CmsPageSiteMapProvider.cs @@ -77,24 +77,16 @@ public override SiteMapNodeCollection GetChildNodes(SiteMapNode node) { Verify.ArgumentNotNull(node, nameof(node)); - var childNodes = new List(); + var childNodes = _plugins.SelectMany(plugin => plugin.GetChildNodes(node) + ?? Enumerable.Empty()).ToList(); - foreach (var plugin in _plugins) - { - var pluginChildNodes = plugin.GetChildNodes(node); - if (pluginChildNodes != null) - { - childNodes.AddRange(pluginChildNodes); - } - } + childNodes = SecurityTrimList(childNodes); if (!childNodes.Any()) { return EmptyCollection; } - childNodes = SecurityTrimList(childNodes); - return new SiteMapNodeCollection(childNodes.ToArray()); } @@ -139,7 +131,7 @@ protected override SiteMapNode GetRootNodeCore() { var pageNode = data.SitemapNavigator.GetPageNodeByHostname(context.Request.Url.Host); - homePageId = pageNode.Id; + homePageId = pageNode?.Id ?? Guid.Empty; } } } @@ -203,16 +195,7 @@ public override bool IsAccessibleToUser(HttpContext ctx, SiteMapNode node) { var ctxBase = new HttpContextWrapper(ctx); - foreach (var plugin in _plugins) - { - var isAccessibleToUser = plugin.IsAccessibleToUser(ctxBase, node); - if (!isAccessibleToUser) - { - return false; - } - } - - return true; + return _plugins.All(plugin => plugin.IsAccessibleToUser(ctxBase, node)); } private T SecurityTrimNode(T node) where T : SiteMapNode @@ -241,7 +224,7 @@ private List SecurityTrimList(List list) where T : SiteMapNode return null; } - if (SecurityTrimmingEnabled) + if (SecurityTrimmingEnabled && list.Count > 0) { var context = HttpContext.Current; diff --git a/Composite/AspNet/ICmsSiteMapNode.cs b/Composite/AspNet/ICmsSiteMapNode.cs index f2ad6a1bc1..ea5a3c0c49 100644 --- a/Composite/AspNet/ICmsSiteMapNode.cs +++ b/Composite/AspNet/ICmsSiteMapNode.cs @@ -1,14 +1,15 @@ using System.Globalization; +using System.Web; namespace Composite.AspNet { /// - /// + /// Used as an extension to when building a ASP.NET sitemap based on cms pages. /// public interface ICmsSiteMapNode { /// - /// Gets or sets the culture. + /// Gets the culture to which the site map node belongs. /// /// /// The culture. diff --git a/Composite/AspNet/ICmsSiteMapProvider.cs b/Composite/AspNet/ICmsSiteMapProvider.cs index 4597d1965e..ce40896da1 100644 --- a/Composite/AspNet/ICmsSiteMapProvider.cs +++ b/Composite/AspNet/ICmsSiteMapProvider.cs @@ -2,10 +2,14 @@ namespace Composite.AspNet { - /// + /// + /// An inteface for getting site map data required for rendering /Sitemap.xml file. + /// public interface ICmsSiteMapProvider { - /// + /// + /// Gets the root nodes. + /// ICollection GetRootNodes(); } } diff --git a/Composite/AspNet/ISchemaOrgSiteMapNode.cs b/Composite/AspNet/ISchemaOrgSiteMapNode.cs index 935c54e842..7a383c177e 100644 --- a/Composite/AspNet/ISchemaOrgSiteMapNode.cs +++ b/Composite/AspNet/ISchemaOrgSiteMapNode.cs @@ -1,22 +1,24 @@ using System; +using System.Web; namespace Composite.AspNet { /// - /// + /// Provides infomation that is used as an addition to when generating sitemap xml file. + /// See https://www.sitemaps.org for details /// public interface ISchemaOrgSiteMapNode { /// - /// Gets or sets the last modified. + /// Gets the last modification time. /// /// - /// The last modified. + /// The last modification time. /// DateTime LastModified { get; } /// - /// Gets or sets the change frequency. + /// Gets the change frequency. /// /// /// The change frequency. @@ -24,7 +26,7 @@ public interface ISchemaOrgSiteMapNode SiteMapNodeChangeFrequency? ChangeFrequency { get; } /// - /// Gets or sets the priority. + /// Gets the priority. /// /// /// The priority. diff --git a/Composite/AspNet/ISiteMapPlugin.cs b/Composite/AspNet/ISiteMapPlugin.cs index 273c77121d..844447d8c9 100644 --- a/Composite/AspNet/ISiteMapPlugin.cs +++ b/Composite/AspNet/ISiteMapPlugin.cs @@ -9,21 +9,21 @@ namespace Composite.AspNet public interface ISiteMapPlugin { /// - /// + /// Retrieves the child nodes of a specific . /// /// /// List GetChildNodes(SiteMapNode node); /// - /// + /// Retrieves the parent node of a specific object. /// /// /// SiteMapNode GetParentNode(SiteMapNode node); /// - /// + /// Retrieves a object that represents a page. /// /// /// @@ -31,7 +31,7 @@ public interface ISiteMapPlugin SiteMapNode FindSiteMapNode(SiteMapProvider provider, string rawUrl); /// - /// + /// Retrieves a object based on . /// /// /// @@ -39,7 +39,7 @@ public interface ISiteMapPlugin SiteMapNode FindSiteMapNode(SiteMapProvider provider, HttpContextBase context); /// - /// + /// Retrieves a object based on a specified key. /// /// /// @@ -47,7 +47,7 @@ public interface ISiteMapPlugin SiteMapNode FindSiteMapNodeFromKey(SiteMapProvider provider, string key); /// - /// + /// Retrieves a Boolean value indicating whether the specified object can be viewed by the user in the specified context. /// /// /// diff --git a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs index 805423a630..081dcdf881 100644 --- a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs +++ b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs @@ -158,9 +158,7 @@ public static void Application_End(object sender, EventArgs e) throw; } - Log.LogVerbose("Global.asax", string.Format("--- Web Application End, {0} Id = {1}---", - DateTime.Now.ToLongTimeString(), - AppDomain.CurrentDomain.Id)); + Log.LogVerbose("Global.asax", $"--- Web Application End, {DateTime.Now.ToLongTimeString()} Id = {AppDomain.CurrentDomain.Id}---"); } } @@ -168,7 +166,7 @@ public static void Application_End(object sender, EventArgs e) /// public static void Application_BeginRequest(object sender, EventArgs e) { - var context = (sender as HttpApplication).Context; + var context = ((HttpApplication) sender).Context; ThreadDataManager.InitializeThroughHttpContext(); @@ -188,7 +186,7 @@ public static void Application_BeginRequest(object sender, EventArgs e) /// public static void Application_EndRequest(object sender, EventArgs e) { - var context = (sender as HttpApplication).Context; + var context = ((HttpApplication) sender).Context; try { @@ -198,7 +196,7 @@ public static void Application_EndRequest(object sender, EventArgs e) { int startTimer = (int)context.Items["Global.asax timer"]; string requestPath = context.Request.Path; - Log.LogVerbose("End request", string.Format("{0} - took {1} ms", requestPath, (Environment.TickCount - startTimer))); + Log.LogVerbose("End request", $"{requestPath} - took {Environment.TickCount - startTimer} ms"); } } finally @@ -212,7 +210,7 @@ public static void Application_EndRequest(object sender, EventArgs e) /// public static void Application_Error(object sender, EventArgs e) { - var httpApplication = (sender as HttpApplication); + var httpApplication = (HttpApplication) sender; Exception exception = httpApplication.Server.GetLastError(); var eventType = TraceEventType.Error; @@ -221,7 +219,8 @@ public static void Application_Error(object sender, EventArgs e) if (httpContext != null) { - bool is404 = (exception is HttpException && ((HttpException)exception).GetHttpCode() == 404); + bool is404 = exception is HttpException httpException + && httpException.GetHttpCode() == 404; if (is404) { @@ -235,7 +234,7 @@ public static void Application_Error(object sender, EventArgs e) { if (rawUrl == customPageNotFoundUrl) { - throw new HttpException(500, "'Page not found' url isn't handled. Url: '{0}'".FormatWith(rawUrl)); + throw new HttpException(500, $"'Page not found' url isn't handled. Url: '{rawUrl}'"); } httpContext.Server.ClearError(); @@ -267,8 +266,7 @@ public static void Application_Error(object sender, EventArgs e) if (request != null) { LoggingService.LogEntry("Application Error", - "Failed to process '{0}' request to url '{1}'" - .FormatWith(request.RequestType, request.RawUrl), + $"Failed to process '{request.RequestType}' request to url '{request.RawUrl}'", LoggingService.Category.General, eventType); } } @@ -372,7 +370,7 @@ private static void CurrentDomain_DomainUnload(object sender, EventArgs e) { if (RuntimeInformation.IsDebugBuild) { - Log.LogInformation(_verboseLogEntryTitle, "AppDomain {0} unloaded at {1}", AppDomain.CurrentDomain.Id, DateTime.Now.ToString("HH:mm:ss:ff")); + Log.LogInformation(_verboseLogEntryTitle, $"AppDomain {AppDomain.CurrentDomain.Id} unloaded at {DateTime.Now:HH:mm:ss:ff}"); } } @@ -404,9 +402,8 @@ private static void LogShutDownReason() System.Reflection.BindingFlags.GetField, null, runtime, null); - Log.LogVerbose("RGB(250,50,50)ASP.NET Shut Down", String.Format("_shutDownMessage=\n{0}\n\n_shutDownStack=\n{1}", - shutDownMessage.Replace("\n", " \n"), - shutDownStack)); + Log.LogVerbose("RGB(250,50,50)ASP.NET Shut Down", + $"_shutDownMessage=\n{shutDownMessage.Replace("\n", " \n")}\n\n_shutDownStack=\n{shutDownStack}"); } } From 0c2a6377c9b62e92bb1c1e3cbea6698bc743df09 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 18 Oct 2018 14:18:36 +0200 Subject: [PATCH 28/51] Cleaning up DataFacade --- Composite/Data/Caching/CachingQueryable.cs | 6 +- Composite/Data/DataFacade.cs | 182 ++++++++------------- 2 files changed, 71 insertions(+), 117 deletions(-) diff --git a/Composite/Data/Caching/CachingQueryable.cs b/Composite/Data/Caching/CachingQueryable.cs index d9cf6cc4a7..3b4eb61468 100644 --- a/Composite/Data/Caching/CachingQueryable.cs +++ b/Composite/Data/Caching/CachingQueryable.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -43,14 +43,14 @@ internal interface ICachedQuery /// /// Used for optimizing execution of GetDataByUniqueKey method /// - internal interface CachingQueryable_CachedByKey + internal interface ICachingQueryable_CachedByKey { IData GetCachedValueByKey(object key); IEnumerable GetCachedVersionValuesByKey(object key); } - internal sealed class CachingQueryable : ICachedQuery, IOrderedQueryable, IQueryProvider, ICachingQueryable, CachingQueryable_CachedByKey + internal sealed class CachingQueryable : ICachedQuery, IOrderedQueryable, IQueryProvider, ICachingQueryable, ICachingQueryable_CachedByKey { private readonly IQueryable _source; private readonly Expression _currentExpression; diff --git a/Composite/Data/DataFacade.cs b/Composite/Data/DataFacade.cs index 4af13480df..bfbe88ff00 100644 --- a/Composite/Data/DataFacade.cs +++ b/Composite/Data/DataFacade.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Globalization; @@ -461,9 +461,7 @@ public static Expression> GetPredicateExpressionByUniqueKey(Dat Expression currentExpression = GetPredicateExpressionByUniqueKeyFilterExpression(keyProperties, dataKeyPropertyCollection, parameterExpression); - Expression> lambdaExpression = Expression.Lambda>(currentExpression, new ParameterExpression[] { parameterExpression }); - - return lambdaExpression; + return Expression.Lambda>(currentExpression, parameterExpression); } @@ -473,12 +471,7 @@ public static Expression> GetPredicateExpressionByUniqueKey(Dat public static Expression> GetPredicateExpressionByUniqueKey(object dataKeyValue) where T : class, IData { - PropertyInfo propertyInfo = DataAttributeFacade.GetKeyProperties(typeof(T)).Single(); - - var dataKeyPropertyCollection = new DataKeyPropertyCollection(); - dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); - - return GetPredicateExpressionByUniqueKey(dataKeyPropertyCollection); + return GetPredicateExpressionByUniqueKey(ToKeyCollection(typeof(T), dataKeyValue)); } @@ -495,11 +488,9 @@ public static LambdaExpression GetPredicateExpressionByUniqueKey(Type interfaceT Expression currentExpression = GetPredicateExpressionByUniqueKeyFilterExpression(keyProperties, dataKeyPropertyCollection, parameterExpression); - Type delegateType = typeof(Func<,>).MakeGenericType(new [] { interfaceType, typeof(bool) }); + Type delegateType = typeof(Func<,>).MakeGenericType(interfaceType, typeof(bool)); - LambdaExpression lambdaExpression = Expression.Lambda(delegateType, currentExpression, new [] { parameterExpression }); - - return lambdaExpression; + return Expression.Lambda(delegateType, currentExpression, parameterExpression); } @@ -510,12 +501,7 @@ public static LambdaExpression GetPredicateExpressionByUniqueKey(Type interfaceT { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - PropertyInfo propertyInfo = DataAttributeFacade.GetKeyProperties(interfaceType).Single(); - - var dataKeyPropertyCollection = new DataKeyPropertyCollection(); - dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); - - return GetPredicateExpressionByUniqueKey(interfaceType, dataKeyPropertyCollection); + return GetPredicateExpressionByUniqueKey(interfaceType, ToKeyCollection(interfaceType, dataKeyValue)); } @@ -548,12 +534,7 @@ private static Expression GetPredicateExpressionByUniqueKeyFilterExpression(IRea public static T TryGetDataByUniqueKey(object dataKeyValue) where T : class, IData { - PropertyInfo propertyInfo = DataAttributeFacade.GetKeyProperties(typeof(T)).Single(); - - var dataKeyPropertyCollection = new DataKeyPropertyCollection(); - dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); - - return TryGetDataByUniqueKey(dataKeyPropertyCollection); + return TryGetDataByUniqueKey(ToKeyCollection(typeof(T), dataKeyValue)); } @@ -566,9 +547,9 @@ public static T TryGetDataByUniqueKey(DataKeyPropertyCollection dataKeyProper IQueryable query = GetData(); - if (query is CachingQueryable_CachedByKey && dataKeyPropertyCollection.Count == 1) + if (query is ICachingQueryable_CachedByKey cachedQuery && dataKeyPropertyCollection.Count == 1) { - return (T) (query as CachingQueryable_CachedByKey).GetCachedValueByKey(dataKeyPropertyCollection.KeyProperties.Single().Value); + return (T)cachedQuery.GetCachedValueByKey(dataKeyPropertyCollection.KeyProperties.Single().Value); } Expression> lambdaExpression = GetPredicateExpressionByUniqueKey(dataKeyPropertyCollection); @@ -585,7 +566,7 @@ public static T GetDataByUniqueKey(object dataKeyValue) { IData data = TryGetDataByUniqueKey(dataKeyValue); - if (data == null) throw new InvalidOperationException("No data exist given the data key values"); + if (data == null) throw new InvalidOperationException("No data exist given the data key value"); return (T)data; } @@ -595,24 +576,18 @@ public static T GetDataByUniqueKey(object dataKeyValue) /// public static IData TryGetDataByUniqueKey(Type interfaceType, object dataKeyValue) { - Verify.ArgumentNotNull(interfaceType, "interfaceType"); + Verify.ArgumentNotNull(interfaceType, nameof(interfaceType)); - if(DataCachingFacade.IsDataAccessCacheEnabled(interfaceType)) + if (DataCachingFacade.IsDataAccessCacheEnabled(interfaceType)) { - var cachedByKey = DataFacade.GetData(interfaceType) as CachingQueryable_CachedByKey; - if(cachedByKey != null) + var query = GetData(interfaceType); + if (query is ICachingQueryable_CachedByKey cachedByKey) { return cachedByKey.GetCachedValueByKey(dataKeyValue); } } - - PropertyInfo propertyInfo = DataAttributeFacade.GetKeyProperties(interfaceType).Single(); - - DataKeyPropertyCollection dataKeyPropertyCollection = new DataKeyPropertyCollection(); - dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); - - return TryGetDataByUniqueKey(interfaceType, dataKeyPropertyCollection); + return TryGetDataByUniqueKey(interfaceType, ToKeyCollection(interfaceType, dataKeyValue)); } // Overload @@ -621,24 +596,20 @@ public static IEnumerable TryGetDataVersionsByUniqueKey(Type interfaceTyp { Verify.ArgumentNotNull(interfaceType, "interfaceType"); + // TODO: move cache lookup into the overload that takes the DataKeyCollection if (DataCachingFacade.IsDataAccessCacheEnabled(interfaceType)) { - var cachedByKey = DataFacade.GetData(interfaceType) as CachingQueryable_CachedByKey; - if (cachedByKey != null) + var query = GetData(interfaceType); + if (query is ICachingQueryable_CachedByKey cachedByKey) { return cachedByKey.GetCachedVersionValuesByKey(dataKeyValue); } } - - PropertyInfo propertyInfo = DataAttributeFacade.GetKeyProperties(interfaceType).Single(); - - DataKeyPropertyCollection dataKeyPropertyCollection = new DataKeyPropertyCollection(); - dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); - - return TryGetDataVersionsByUniqueKey(interfaceType, dataKeyPropertyCollection); + return TryGetDataVersionsByUniqueKey(interfaceType, ToKeyCollection(interfaceType, dataKeyValue)); } + /// public static IData TryGetDataByUniqueKey(Type interfaceType, DataKeyPropertyCollection dataKeyPropertyCollection) { @@ -677,14 +648,9 @@ public static IData GetDataByUniqueKey(Type interfaceType, object dataKeyValue) { Verify.ArgumentNotNull(interfaceType, nameof(interfaceType)); - PropertyInfo propertyInfo = interfaceType.GetKeyProperties().Single(); - - var dataKeyPropertyCollection = new DataKeyPropertyCollection(); - dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); - - IData data = TryGetDataByUniqueKey(interfaceType, dataKeyPropertyCollection); + IData data = TryGetDataByUniqueKey(interfaceType, dataKeyValue); - Verify.IsNotNull(data, "No data exist given the data key values"); + Verify.IsNotNull(data, "No data exist given the data key value"); return data; } @@ -704,6 +670,22 @@ public static IData GetDataByUniqueKey(Type interfaceType, DataKeyPropertyCollec return data; } + + + private static DataKeyPropertyCollection ToKeyCollection(Type interfaceType, object dataKeyValue) + { + var keyPropertyInfo = interfaceType.GetKeyProperties().Single(); + return ToKeyCollection(keyPropertyInfo, dataKeyValue); + } + + + private static DataKeyPropertyCollection ToKeyCollection(PropertyInfo keyPropertyInfo, object dataKeyValue) + { + var dataKeyPropertyCollection = new DataKeyPropertyCollection(); + dataKeyPropertyCollection.AddKeyProperty(keyPropertyInfo, dataKeyValue); + return dataKeyPropertyCollection; + } + #endregion @@ -1757,9 +1739,9 @@ public static bool HasDataInAnyScope(Type interfaceType) { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - foreach (DataScopeIdentifier dataScopeIdentifier in GetSupportedDataScopes(interfaceType)) + foreach (var dataScopeIdentifier in GetSupportedDataScopes(interfaceType)) { - using (DataScope dataScope = new DataScope(dataScopeIdentifier)) + using (new DataScope(dataScopeIdentifier)) { IData data = GetData(interfaceType).ToDataEnumerable().FirstOrDefault(); @@ -1799,7 +1781,7 @@ public static bool ExistsInAnyLocale(CultureInfo excludedCultureInfo) public static bool ExistsInAnyLocale() where T : class, IData { - return ExistsInAnyLocale(new CultureInfo[] { }); + return ExistsInAnyLocale(Enumerable.Empty()); } @@ -1811,7 +1793,7 @@ public static bool ExistsInAnyLocale(Type interfaceType) MethodInfo methodInfo = GetExistsInAnyLocaleMethodInfo(interfaceType); - bool result = (bool)methodInfo.Invoke(null, new object[] { }); + bool result = (bool)methodInfo.Invoke(null, null); return result; } @@ -1849,13 +1831,13 @@ public static MethodInfo GetSetDataInterceptorMethodInfo(Type interfaceType) if (_resourceLocker.Resources.GenericSetDataInterceptorMethodInfo.TryGetValue(interfaceType, out methodInfo) == false) { MethodInfo nonGenericMethod = typeof(DataFacade).GetMethod( - "SetDataInterceptor", + nameof(SetDataInterceptor), BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(DataInterceptor) }, null); - methodInfo = nonGenericMethod.MakeGenericMethod(new Type[] { interfaceType }); + methodInfo = nonGenericMethod.MakeGenericMethod(interfaceType); _resourceLocker.Resources.GenericSetDataInterceptorMethodInfo.Add(interfaceType, methodInfo); } @@ -1870,7 +1852,7 @@ public static MethodInfo GetSetDataInterceptorMethodInfo(Type interfaceType) public static MethodInfo GetHasDataInterceptorMethodInfo(Type interfaceType) { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - if (typeof(IData).IsAssignableFrom(interfaceType) == false) throw new ArgumentException("The provided type must implement IData", "interfaceType"); + if (!typeof(IData).IsAssignableFrom(interfaceType)) throw new ArgumentException("The provided type must implement IData", "interfaceType"); MethodInfo methodInfo; using (_resourceLocker.Locker) @@ -1878,13 +1860,13 @@ public static MethodInfo GetHasDataInterceptorMethodInfo(Type interfaceType) if (_resourceLocker.Resources.GenericHasDataInterceptorMethodInfo.TryGetValue(interfaceType, out methodInfo) == false) { MethodInfo nonGenericMethod = typeof(DataFacade).GetMethod( - "HasDataInterceptor", + nameof(HasDataInterceptor), BindingFlags.Static | BindingFlags.Public, null, new Type[] { }, null); - methodInfo = nonGenericMethod.MakeGenericMethod(new Type[] { interfaceType }); + methodInfo = nonGenericMethod.MakeGenericMethod(interfaceType); _resourceLocker.Resources.GenericHasDataInterceptorMethodInfo.Add(interfaceType, methodInfo); } @@ -1899,7 +1881,7 @@ public static MethodInfo GetHasDataInterceptorMethodInfo(Type interfaceType) public static MethodInfo GetClearDataInterceptorMethodInfo(Type interfaceType) { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - if (typeof(IData).IsAssignableFrom(interfaceType) == false) throw new ArgumentException("The provided type must implement IData", "interfaceType"); + if (!typeof(IData).IsAssignableFrom(interfaceType)) throw new ArgumentException("The provided type must implement IData", "interfaceType"); MethodInfo methodInfo; using (_resourceLocker.Locker) @@ -1907,13 +1889,13 @@ public static MethodInfo GetClearDataInterceptorMethodInfo(Type interfaceType) if (_resourceLocker.Resources.GenericClearDataInterceptorMethodInfo.TryGetValue(interfaceType, out methodInfo) == false) { MethodInfo nonGenericMethod = typeof(DataFacade).GetMethod( - "ClearDataInterceptor", + nameof(ClearDataInterceptor), BindingFlags.Static | BindingFlags.Public, null, new Type[] { }, null); - methodInfo = nonGenericMethod.MakeGenericMethod(new Type[] { interfaceType }); + methodInfo = nonGenericMethod.MakeGenericMethod(interfaceType); _resourceLocker.Resources.GenericClearDataInterceptorMethodInfo.Add(interfaceType, methodInfo); } @@ -1928,7 +1910,7 @@ public static MethodInfo GetClearDataInterceptorMethodInfo(Type interfaceType) public static MethodInfo GetGetDataMethodInfo(Type interfaceType) { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - if (typeof(IData).IsAssignableFrom(interfaceType) == false) throw new ArgumentException("The provided type must implement IData", "interfaceType"); + if (!typeof(IData).IsAssignableFrom(interfaceType)) throw new ArgumentException("The provided type must implement IData", "interfaceType"); MethodInfo methodInfo; using (_resourceLocker.Locker) @@ -1936,13 +1918,13 @@ public static MethodInfo GetGetDataMethodInfo(Type interfaceType) if (_resourceLocker.Resources.GenericGetDataFromTypeMethodInfo.TryGetValue(interfaceType, out methodInfo) == false) { MethodInfo nonGenericMethod = typeof(DataFacade).GetMethod( - "GetData", + nameof(GetData), BindingFlags.Static | BindingFlags.Public, null, - new Type[] { typeof(bool), typeof(IEnumerable) }, + new[] { typeof(bool), typeof(IEnumerable) }, null); - methodInfo = nonGenericMethod.MakeGenericMethod(new Type[] { interfaceType }); + methodInfo = nonGenericMethod.MakeGenericMethod(interfaceType); _resourceLocker.Resources.GenericGetDataFromTypeMethodInfo.Add(interfaceType, methodInfo); } @@ -1957,7 +1939,7 @@ public static MethodInfo GetGetDataMethodInfo(Type interfaceType) public static MethodInfo GetGetDataFromDataSourceIdMethodInfo(Type interfaceType) { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - if (typeof(IData).IsAssignableFrom(interfaceType) == false) throw new ArgumentException("The provided type must implement IData", "interfaceType"); + if (!typeof(IData).IsAssignableFrom(interfaceType)) throw new ArgumentException("The provided type must implement IData", "interfaceType"); MethodInfo methodInfo; using (_resourceLocker.Locker) @@ -1966,12 +1948,12 @@ public static MethodInfo GetGetDataFromDataSourceIdMethodInfo(Type interfaceType { MethodInfo nonGenericMethod = (from m in typeof(DataFacade).GetMethods(BindingFlags.Public | BindingFlags.Static) - where m.Name == "GetDataFromDataSourceId" && + where m.Name == nameof(GetDataFromDataSourceId) && m.IsGenericMethodDefinition && m.GetParameters().Length == 2 select m).Single(); - methodInfo = nonGenericMethod.MakeGenericMethod(new Type[] { interfaceType }); + methodInfo = nonGenericMethod.MakeGenericMethod(interfaceType); _resourceLocker.Resources.GenericGetDataFromDataSourceIdMethodInfo.Add(interfaceType, methodInfo); } @@ -1986,7 +1968,7 @@ public static MethodInfo GetGetDataFromDataSourceIdMethodInfo(Type interfaceType public static MethodInfo GetGetDataWithPredicatMethodInfo(Type interfaceType) { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - if (typeof(IData).IsAssignableFrom(interfaceType) == false) throw new ArgumentException("The provided type must implement IData", "interfaceType"); + if (!typeof(IData).IsAssignableFrom(interfaceType)) throw new ArgumentException("The provided type must implement IData", "interfaceType"); MethodInfo methodInfo; using (_resourceLocker.Locker) @@ -1995,14 +1977,14 @@ public static MethodInfo GetGetDataWithPredicatMethodInfo(Type interfaceType) { MethodInfo genericMethod = (from m in typeof(DataFacade).GetMethods(BindingFlags.Public | BindingFlags.Static) - where m.Name == "GetData" && + where m.Name == nameof(GetData) && m.IsGenericMethodDefinition && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsGenericType && m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>) select m).Single(); - methodInfo = genericMethod.MakeGenericMethod(new Type[] { interfaceType }); + methodInfo = genericMethod.MakeGenericMethod(interfaceType); _resourceLocker.Resources.GenericGetDataFromTypeWithPredicateMethodInfo.Add(interfaceType, methodInfo); } @@ -2026,7 +2008,7 @@ public static MethodInfo GetAddNewMethodInfo(Type interfaceType) { MethodInfo genericMethodInfo = StaticReflection.GetGenericMethodInfo(data => AddNew((IData) null, true, true, true, true, null)); - methodInfo = genericMethodInfo.MakeGenericMethod(new [] { interfaceType }); + methodInfo = genericMethodInfo.MakeGenericMethod(interfaceType); _resourceLocker.Resources.GenericAddNewFromTypeMethodInfo.Add(interfaceType, methodInfo); } @@ -2037,34 +2019,6 @@ public static MethodInfo GetAddNewMethodInfo(Type interfaceType) - /// - public static MethodInfo GetMoveMethodInfo(Type interfaceType) - { - if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - if (typeof(IData).IsAssignableFrom(interfaceType) == false) throw new ArgumentException("The provided type must implement IData", "interfaceType"); - - MethodInfo methodInfo; - using (_resourceLocker.Locker) - { - if (_resourceLocker.Resources.GenericMoveFromTypeMethodInfo.TryGetValue(interfaceType, out methodInfo) == false) - { - methodInfo = - (from method in typeof(DataFacade).GetMethods(BindingFlags.Static | BindingFlags.NonPublic) - where method.Name == "Move" && - method.GetParameters().Length == 3 - select method).First(); - - methodInfo = methodInfo.MakeGenericMethod(new Type[] { interfaceType }); - - _resourceLocker.Resources.GenericMoveFromTypeMethodInfo.Add(interfaceType, methodInfo); - } - } - - return methodInfo; - } - - - /// public static MethodInfo GetExistsInAnyLocaleMethodInfo(Type interfaceType) { @@ -2078,11 +2032,11 @@ public static MethodInfo GetExistsInAnyLocaleMethodInfo(Type interfaceType) { methodInfo = (from method in typeof(DataFacade).GetMethods(BindingFlags.Static | BindingFlags.NonPublic) - where method.Name == "ExistsInAnyLocale" && + where method.Name == nameof(ExistsInAnyLocale) && typeof(IEnumerable).IsAssignableFrom(method.GetParameters()[0].ParameterType) == false select method).First(); - methodInfo = methodInfo.MakeGenericMethod(new Type[] { interfaceType }); + methodInfo = methodInfo.MakeGenericMethod(interfaceType); _resourceLocker.Resources.GenericExistsInAnyLocaleMethodInfo.Add(interfaceType, methodInfo); } @@ -2097,7 +2051,7 @@ public static MethodInfo GetExistsInAnyLocaleMethodInfo(Type interfaceType) public static MethodInfo GetExistsInAnyLocaleWithParamMethodInfo(Type interfaceType) { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - if (typeof(IData).IsAssignableFrom(interfaceType) == false) throw new ArgumentException("The provided type must implement IData", "interfaceType"); + if (!typeof(IData).IsAssignableFrom(interfaceType)) throw new ArgumentException("The provided type must implement IData", "interfaceType"); MethodInfo methodInfo; using (_resourceLocker.Locker) @@ -2107,12 +2061,12 @@ public static MethodInfo GetExistsInAnyLocaleWithParamMethodInfo(Type interfaceT methodInfo = (from method in typeof(DataFacade).GetMethods(BindingFlags.Static | BindingFlags.Public) where - (method.Name == "ExistsInAnyLocale") && - (method.GetParameters().Count() == 1) && - (typeof(IEnumerable).IsAssignableFrom(method.GetParameters()[0].ParameterType)) + method.Name == nameof(ExistsInAnyLocale) && + method.GetParameters().Length == 1 && + typeof(IEnumerable).IsAssignableFrom(method.GetParameters()[0].ParameterType) select method).First(); - methodInfo = methodInfo.MakeGenericMethod(new Type[] { interfaceType }); + methodInfo = methodInfo.MakeGenericMethod(interfaceType); _resourceLocker.Resources.GenericExistsInAnyLocaleWithParamMethodInfo.Add(interfaceType, methodInfo); } From cb3e73cdd5e0e0c440336a66579605565d326fef Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 18 Oct 2018 14:48:30 +0200 Subject: [PATCH 29/51] Fix #618 DataFacade.GetDataByUniqueKey requiring key value to match expected type but only when caching is enabled --- Composite/Data/DataFacade.cs | 73 +++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/Composite/Data/DataFacade.cs b/Composite/Data/DataFacade.cs index bfbe88ff00..9b42e89ab7 100644 --- a/Composite/Data/DataFacade.cs +++ b/Composite/Data/DataFacade.cs @@ -543,18 +543,7 @@ public static T TryGetDataByUniqueKey(object dataKeyValue) public static T TryGetDataByUniqueKey(DataKeyPropertyCollection dataKeyPropertyCollection) where T : class, IData { - Verify.ArgumentNotNull(dataKeyPropertyCollection, "dataKeyPropertyCollection"); - - IQueryable query = GetData(); - - if (query is ICachingQueryable_CachedByKey cachedQuery && dataKeyPropertyCollection.Count == 1) - { - return (T)cachedQuery.GetCachedValueByKey(dataKeyPropertyCollection.KeyProperties.Single().Value); - } - - Expression> lambdaExpression = GetPredicateExpressionByUniqueKey(dataKeyPropertyCollection); - - return query.FirstOrDefault(lambdaExpression); + return (T) TryGetDataByUniqueKey(typeof(T), dataKeyPropertyCollection); } @@ -578,15 +567,6 @@ public static IData TryGetDataByUniqueKey(Type interfaceType, object dataKeyValu { Verify.ArgumentNotNull(interfaceType, nameof(interfaceType)); - if (DataCachingFacade.IsDataAccessCacheEnabled(interfaceType)) - { - var query = GetData(interfaceType); - if (query is ICachingQueryable_CachedByKey cachedByKey) - { - return cachedByKey.GetCachedValueByKey(dataKeyValue); - } - } - return TryGetDataByUniqueKey(interfaceType, ToKeyCollection(interfaceType, dataKeyValue)); } @@ -596,16 +576,6 @@ public static IEnumerable TryGetDataVersionsByUniqueKey(Type interfaceTyp { Verify.ArgumentNotNull(interfaceType, "interfaceType"); - // TODO: move cache lookup into the overload that takes the DataKeyCollection - if (DataCachingFacade.IsDataAccessCacheEnabled(interfaceType)) - { - var query = GetData(interfaceType); - if (query is ICachingQueryable_CachedByKey cachedByKey) - { - return cachedByKey.GetCachedVersionValuesByKey(dataKeyValue); - } - } - return TryGetDataVersionsByUniqueKey(interfaceType, ToKeyCollection(interfaceType, dataKeyValue)); } @@ -616,6 +586,15 @@ public static IData TryGetDataByUniqueKey(Type interfaceType, DataKeyPropertyCol if (interfaceType == null) throw new ArgumentNullException("interfaceType"); if (dataKeyPropertyCollection == null) throw new ArgumentNullException("dataKeyPropertyCollection"); + if (dataKeyPropertyCollection.Count == 1 && DataCachingFacade.IsDataAccessCacheEnabled(interfaceType)) + { + var query = GetData(interfaceType); + if (query is ICachingQueryable_CachedByKey cachedByKey) + { + return cachedByKey.GetCachedValueByKey(GetConvertedUniqueKey(interfaceType, dataKeyPropertyCollection)); + } + } + LambdaExpression lambdaExpression = GetPredicateExpressionByUniqueKey(interfaceType, dataKeyPropertyCollection); MethodInfo methodInfo = GetGetDataWithPredicatMethodInfo(interfaceType); @@ -627,12 +606,22 @@ public static IData TryGetDataByUniqueKey(Type interfaceType, DataKeyPropertyCol return data; } + /// public static IEnumerable TryGetDataVersionsByUniqueKey(Type interfaceType, DataKeyPropertyCollection dataKeyPropertyCollection) { Verify.ArgumentNotNull(interfaceType, nameof(interfaceType)); Verify.ArgumentNotNull(dataKeyPropertyCollection, nameof(dataKeyPropertyCollection)); + if (dataKeyPropertyCollection.Count == 1 && DataCachingFacade.IsDataAccessCacheEnabled(interfaceType)) + { + var query = GetData(interfaceType); + if (query is ICachingQueryable_CachedByKey cachedByKey) + { + return cachedByKey.GetCachedVersionValuesByKey(GetConvertedUniqueKey(interfaceType, dataKeyPropertyCollection)); + } + } + LambdaExpression lambdaExpression = GetPredicateExpressionByUniqueKey(interfaceType, dataKeyPropertyCollection); MethodInfo methodInfo = GetGetDataWithPredicatMethodInfo(interfaceType); @@ -642,6 +631,28 @@ public static IEnumerable TryGetDataVersionsByUniqueKey(Type interfaceTyp return ((IEnumerable) queryable).Cast(); } + + private static object GetConvertedUniqueKey(Type interfaceType, DataKeyPropertyCollection keyCollection) + { + var keyPropertyInfo = interfaceType.GetKeyProperties().Single(); + var kvp = keyCollection.KeyProperties.Single(); + + if (keyPropertyInfo.Name != kvp.Key) + { + throw new InvalidOperationException($"Expected value for key '{keyPropertyInfo.Name}' but found '{kvp.Key}'"); + } + + + var result = ValueTypeConverter.TryConvert(kvp.Value, keyPropertyInfo.PropertyType, out var conversionException); + if (conversionException != null) + { + throw conversionException; + } + + return result; + } + + // Overload /// public static IData GetDataByUniqueKey(Type interfaceType, object dataKeyValue) From f61e297104f9ab0486f7d1811e35844d09bf1a8c Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 18 Oct 2018 14:52:50 +0200 Subject: [PATCH 30/51] Frontend build output added to .gitignore, project file updates (nonfunctional checkin) --- .gitignore | 24 +++++- Website/WebSite.csproj.user | 1 + Website/jspm.config.js | 168 ------------------------------------ 3 files changed, 24 insertions(+), 169 deletions(-) diff --git a/.gitignore b/.gitignore index 95b556a942..11346d87a7 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,26 @@ selenium-debug.log /Website/test/e2e/reports/ -GitCommitInfo.cs \ No newline at end of file +GitCommitInfo.cs + +/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/autolink/plugin.min.js +/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/lists/plugin.min.js +/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/paste/plugin.min.js +/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/table/plugin.min.js +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/fonts/tinymce.woff +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/fonts/tinymce.ttf +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/fonts/tinymce.svg +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/fonts/tinymce.eot +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/fonts/tinymce-small.woff +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/fonts/tinymce-small.ttf +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/fonts/tinymce-small.svg +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/fonts/tinymce-small.eot +/Website/Composite/content/misc/editors/visualeditor/tinymce/tinymce.min.js +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/skin.min.css +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/skin.ie7.min.css +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/img/trans.gif +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/img/object.gif +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/img/loader.gif +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/img/anchor.gif +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/content.min.css +/Website/Composite/content/misc/editors/visualeditor/tinymce/skins/lightgray/content.inline.min.css diff --git a/Website/WebSite.csproj.user b/Website/WebSite.csproj.user index 45a9130d56..8ffd797c3a 100644 --- a/Website/WebSite.csproj.user +++ b/Website/WebSite.csproj.user @@ -3,6 +3,7 @@ ShowAllFiles true + Debug|Any CPU diff --git a/Website/jspm.config.js b/Website/jspm.config.js index 56f3304c3d..5c6da6cdd8 100644 --- a/Website/jspm.config.js +++ b/Website/jspm.config.js @@ -339,124 +339,21 @@ SystemJS.config({ "zlib": "npm:jspm-nodelibs-zlib@0.2.0" }, packages: { - "npm:bn.js@4.11.6": { - "map": {} - }, - "npm:browserify-aes@1.0.6": { - "map": { - "buffer-xor": "npm:buffer-xor@1.0.3", - "cipher-base": "npm:cipher-base@1.0.3", - "create-hash": "npm:create-hash@1.1.2", - "evp_bytestokey": "npm:evp_bytestokey@1.0.0", - "inherits": "npm:inherits@2.0.3" - } - }, - "npm:browserify-cipher@1.0.0": { - "map": { - "browserify-aes": "npm:browserify-aes@1.0.6", - "browserify-des": "npm:browserify-des@1.0.0", - "evp_bytestokey": "npm:evp_bytestokey@1.0.0" - } - }, - "npm:browserify-des@1.0.0": { - "map": { - "cipher-base": "npm:cipher-base@1.0.3", - "des.js": "npm:des.js@1.0.0", - "inherits": "npm:inherits@2.0.3" - } - }, - "npm:browserify-rsa@4.0.1": { - "map": { - "bn.js": "npm:bn.js@4.11.6", - "randombytes": "npm:randombytes@2.0.3" - } - }, - "npm:browserify-sign@4.0.0": { - "map": { - "bn.js": "npm:bn.js@4.11.6", - "browserify-rsa": "npm:browserify-rsa@4.0.1", - "create-hash": "npm:create-hash@1.1.2", - "create-hmac": "npm:create-hmac@1.1.4", - "elliptic": "npm:elliptic@6.3.2", - "inherits": "npm:inherits@2.0.3", - "parse-asn1": "npm:parse-asn1@5.0.0" - } - }, "npm:browserify-zlib@0.1.4": { "map": { "pako": "npm:pako@0.2.9", "readable-stream": "npm:readable-stream@2.2.2" } }, - "npm:buffer-xor@1.0.3": { - "map": {} - }, "npm:core-util-is@1.0.2": { "map": {} }, - "npm:create-ecdh@4.0.0": { - "map": { - "bn.js": "npm:bn.js@4.11.6", - "elliptic": "npm:elliptic@6.3.2" - } - }, - "npm:create-hash@1.1.2": { - "map": { - "cipher-base": "npm:cipher-base@1.0.3", - "inherits": "npm:inherits@2.0.3", - "ripemd160": "npm:ripemd160@1.0.1", - "sha.js": "npm:sha.js@2.4.8" - } - }, - "npm:create-hmac@1.1.4": { - "map": { - "create-hash": "npm:create-hash@1.1.2", - "inherits": "npm:inherits@2.0.3" - } - }, - "npm:crypto-browserify@3.11.0": { - "map": { - "browserify-cipher": "npm:browserify-cipher@1.0.0", - "browserify-sign": "npm:browserify-sign@4.0.0", - "create-ecdh": "npm:create-ecdh@4.0.0", - "create-hash": "npm:create-hash@1.1.2", - "create-hmac": "npm:create-hmac@1.1.4", - "diffie-hellman": "npm:diffie-hellman@5.0.2", - "inherits": "npm:inherits@2.0.3", - "pbkdf2": "npm:pbkdf2@3.0.9", - "public-encrypt": "npm:public-encrypt@4.0.0", - "randombytes": "npm:randombytes@2.0.3" - } - }, - "npm:des.js@1.0.0": { - "map": { - "inherits": "npm:inherits@2.0.3", - "minimalistic-assert": "npm:minimalistic-assert@1.0.0" - } - }, - "npm:diffie-hellman@5.0.2": { - "map": { - "bn.js": "npm:bn.js@4.11.6", - "miller-rabin": "npm:miller-rabin@4.0.0", - "randombytes": "npm:randombytes@2.0.3" - } - }, "npm:domain-browser@1.1.7": { "map": {} }, - "npm:evp_bytestokey@1.0.0": { - "map": { - "create-hash": "npm:create-hash@1.1.2" - } - }, "npm:has-flag@1.0.0": { "map": {} }, - "npm:hash.js@1.0.3": { - "map": { - "inherits": "npm:inherits@2.0.3" - } - }, "npm:iconv-lite@0.4.13": { "map": {} }, @@ -470,12 +367,6 @@ SystemJS.config({ "js-tokens": "npm:js-tokens@1.0.3" } }, - "npm:miller-rabin@4.0.0": { - "map": { - "bn.js": "npm:bn.js@4.11.6", - "brorand": "npm:brorand@1.0.6" - } - }, "npm:normalizr@2.2.1": { "map": { "lodash": "npm:lodash@4.16.4" @@ -484,33 +375,12 @@ SystemJS.config({ "npm:pako@0.2.9": { "map": {} }, - "npm:parse-asn1@5.0.0": { - "map": { - "asn1.js": "npm:asn1.js@4.9.0", - "browserify-aes": "npm:browserify-aes@1.0.6", - "create-hash": "npm:create-hash@1.1.2", - "evp_bytestokey": "npm:evp_bytestokey@1.0.0", - "pbkdf2": "npm:pbkdf2@3.0.9" - } - }, "npm:process-nextick-args@1.0.7": { "map": {} }, - "npm:public-encrypt@4.0.0": { - "map": { - "bn.js": "npm:bn.js@4.11.6", - "browserify-rsa": "npm:browserify-rsa@4.0.1", - "create-hash": "npm:create-hash@1.1.2", - "parse-asn1": "npm:parse-asn1@5.0.0", - "randombytes": "npm:randombytes@2.0.3" - } - }, "npm:punycode@1.3.2": { "map": {} }, - "npm:randombytes@2.0.3": { - "map": {} - }, "npm:react-redux@4.4.5": { "map": { "hoist-non-react-statics": "npm:hoist-non-react-statics@1.2.0", @@ -519,9 +389,6 @@ SystemJS.config({ "loose-envify": "npm:loose-envify@1.2.0" } }, - "npm:ripemd160@1.0.1": { - "map": {} - }, "npm:string_decoder@0.10.31": { "map": {} }, @@ -586,24 +453,6 @@ SystemJS.config({ "isarray": "npm:isarray@1.0.0" } }, - "npm:elliptic@6.3.2": { - "map": { - "bn.js": "npm:bn.js@4.11.6", - "inherits": "npm:inherits@2.0.3", - "brorand": "npm:brorand@1.0.6", - "hash.js": "npm:hash.js@1.0.3" - } - }, - "npm:cipher-base@1.0.3": { - "map": { - "inherits": "npm:inherits@2.0.3" - } - }, - "npm:pbkdf2@3.0.9": { - "map": { - "create-hmac": "npm:create-hmac@1.1.4" - } - }, "npm:rc-table@5.0.3": { "map": { "shallowequal": "npm:shallowequal@0.2.2", @@ -721,18 +570,6 @@ SystemJS.config({ "xtend": "npm:xtend@4.0.1" } }, - "npm:sha.js@2.4.8": { - "map": { - "inherits": "npm:inherits@2.0.3" - } - }, - "npm:asn1.js@4.9.0": { - "map": { - "bn.js": "npm:bn.js@4.11.6", - "inherits": "npm:inherits@2.0.3", - "minimalistic-assert": "npm:minimalistic-assert@1.0.0" - } - }, "npm:wampy@4.0.0": { "main": "build/wampy.js", "map": { @@ -862,11 +699,6 @@ SystemJS.config({ "map": { "url-browserify": "npm:url@0.11.0" } - }, - "github:jspm/nodelibs-crypto@0.2.0-alpha": { - "map": { - "crypto-browserify": "npm:crypto-browserify@3.11.0" - } } } }); From d8739c70c9f48e29815878151180dfc82a794073 Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 18 Oct 2018 14:53:42 +0200 Subject: [PATCH 31/51] Removing "List Unpublished Items/Content" command from "Data | Global Datatypes". --- .../GeneratedDataTypesElementProvider.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Composite/Plugins/Elements/ElementProviders/GeneratedDataTypesElementProvider/GeneratedDataTypesElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/GeneratedDataTypesElementProvider/GeneratedDataTypesElementProvider.cs index 83a27ebc7a..f87ef1fd4f 100644 --- a/Composite/Plugins/Elements/ElementProviders/GeneratedDataTypesElementProvider/GeneratedDataTypesElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/GeneratedDataTypesElementProvider/GeneratedDataTypesElementProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Configuration; using System.Linq; @@ -303,7 +303,9 @@ public IEnumerable GetRoots(SearchToken seachToken) }); } - globalDataElement.AddAction( + if (_websiteItemsView) + { + globalDataElement.AddAction( new ElementAction(new ActionHandle(new ViewUnpublishedItemsActionToken())) { VisualData = new ActionVisualizedData @@ -321,6 +323,7 @@ public IEnumerable GetRoots(SearchToken seachToken) } } }); + } roots.Add(globalDataElement); From 213268890d936f5ea936fe12098e73563ae3bd8a Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 18 Oct 2018 15:02:39 +0200 Subject: [PATCH 32/51] Labels that were "Unpublished Items" and "Unpublished Pages" are now "Unpublished Content". --- .../PageElementProvider/PageElementProvider.cs | 4 ++-- ...ugins.GeneratedDataTypesElementProvider.en-us.xml | 4 ++-- .../Composite.Plugins.PageElementProvider.en-us.xml | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index 646be69878..1fc57b4d6c 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Composite.C1Console.Actions; @@ -168,7 +168,7 @@ var pageType in { VisualData = new ActionVisualizedData { - //Label = "List unpublished Pages and Folder Data", + //Label = "List unpublished Content", //ToolTip = "Get an overview of pages and page folder data that haven't been published yet.", Label = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.ViewUnpublishedItems"), ToolTip = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.ViewUnpublishedItemsToolTip"), diff --git a/Website/Composite/localization/Composite.Plugins.GeneratedDataTypesElementProvider.en-us.xml b/Website/Composite/localization/Composite.Plugins.GeneratedDataTypesElementProvider.en-us.xml index ec154612e3..c0a848294e 100644 --- a/Website/Composite/localization/Composite.Plugins.GeneratedDataTypesElementProvider.en-us.xml +++ b/Website/Composite/localization/Composite.Plugins.GeneratedDataTypesElementProvider.en-us.xml @@ -1,4 +1,4 @@ - + @@ -10,7 +10,7 @@ - + diff --git a/Website/Composite/localization/Composite.Plugins.PageElementProvider.en-us.xml b/Website/Composite/localization/Composite.Plugins.PageElementProvider.en-us.xml index 3d3ba92823..bfb274480b 100644 --- a/Website/Composite/localization/Composite.Plugins.PageElementProvider.en-us.xml +++ b/Website/Composite/localization/Composite.Plugins.PageElementProvider.en-us.xml @@ -1,4 +1,4 @@ - + @@ -62,10 +62,10 @@ - - - - + + + + @@ -112,7 +112,7 @@ - + From f95e113129cefed18d9c080313967e16910d2423 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 18 Oct 2018 17:38:41 +0200 Subject: [PATCH 33/51] Logging errors related to rendering pages with the goal of collecting dymanic url mappers --- Composite/Data/PageRenderingHistory.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Composite/Data/PageRenderingHistory.cs b/Composite/Data/PageRenderingHistory.cs index 2e73cbfc84..fa403d928a 100644 --- a/Composite/Data/PageRenderingHistory.cs +++ b/Composite/Data/PageRenderingHistory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.IO; using System.Net; @@ -18,13 +18,14 @@ namespace Composite.Data ///
public static class PageRenderingHistory { - private static readonly string LogTitle = typeof(PageRenderingHistory).Name; - + private static readonly string LogTitle = nameof(PageRenderingHistory); + private const int MaxRenderErrorsToLog = 10; private const int PageRenderingTimeout = 7000; private const int PageRenderingQueueWaitingTimeout = 15000; - private static readonly ConcurrentDictionary _renderedPages = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary _renderedPages = new ConcurrentDictionary(); + private static int _errorCounter; private static readonly object _pageRenderingLock = new object(); @@ -152,7 +153,10 @@ private static void RenderPage(IPage page) string responseBody, errorMessage; var result = RenderPage(hostName, url, cookies, out responseBody, out errorMessage); - // TODO: log errors if any + if (result != PageRenderingResult.Successful && Interlocked.Increment(ref _errorCounter) <= MaxRenderErrorsToLog) + { + Log.LogWarning(LogTitle, $"Failed to render page '{url}' with the goal of collecting dymanic url providers. Result: {result}; Error: {errorMessage}"); + } } @@ -168,8 +172,6 @@ private static PageRenderingResult RenderPage(string hostname, string url, strin { try { - url = url.Replace("://" + hostname + "/", "://127.0.0.1/"); - var request = WebRequest.Create(url) as HttpWebRequest; request.UserAgent = @"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5"; From 5dd9ad7ad0ba2694590ced326f4ea5a3fd689bd3 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 19 Oct 2018 11:36:51 +0200 Subject: [PATCH 34/51] Cleaning up no longer used code --- .../Renderings/Page/PagePreviewBuilder.cs | 235 ++---------------- 1 file changed, 24 insertions(+), 211 deletions(-) diff --git a/Composite/Core/WebClient/Renderings/Page/PagePreviewBuilder.cs b/Composite/Core/WebClient/Renderings/Page/PagePreviewBuilder.cs index af94224353..a5d50c6175 100644 --- a/Composite/Core/WebClient/Renderings/Page/PagePreviewBuilder.cs +++ b/Composite/Core/WebClient/Renderings/Page/PagePreviewBuilder.cs @@ -1,19 +1,13 @@ -using System; +using System; using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Text; using System.Web; using System.Web.Caching; -using System.Web.Hosting; -using System.Web.SessionState; using Composite.Data.Types; namespace Composite.Core.WebClient.Renderings.Page { /// - /// Allow previewing a page 'in mem' in a simulated GET request. Limited information is passed from original client to this request when - /// running in 'classic mode'. In pipeline mode the original context is available for the preview rendering. + /// Allow previewing a page 'in mem' in a simulated GET request. Requires IIS to run in "Integrated" pipeline mode. /// /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] @@ -22,25 +16,29 @@ public class PagePreviewBuilder private static readonly TimeSpan PreviewExpirationTimeSpan = new TimeSpan(0, 20, 0); /// - /// Execute an 'im mem' preview request of the provided page and content. The execution depends on the hosting environment. - /// Then running in pipeline mode the current HttpContext is + /// Execute an 'im mem' preview request of the provided page and content. Requires IIS to run in "Integrated" pipeline mode. /// /// Page to render. Functionality reading the rendered page ID will get the ID from this object. /// Content to render on the page - /// The page html as a string when running in classic mode. In Pipeline mode the content is written directly to the HttpContext and an empty string is returned. + /// + /// In Pipeline mode the content is written directly to the HttpContext and an empty string is returned. + /// The IIS classic mode is no longer supported and will throw an exception. + /// public static string RenderPreview(IPage selectedPage, IList contents) { return RenderPreview(selectedPage, contents, RenderingReason.PreviewUnsavedChanges); } /// - /// Execute an 'im mem' preview request of the provided page and content. The execution depends on the hosting environment. - /// Then running in pipeline mode the current HttpContext is + /// Execute an 'im mem' preview request of the provided page and content. Requires IIS to run in "Integrated" pipeline mode. /// /// Page to render. Functionality reading the rendered page ID will get the ID from this object. /// Content to render on the page /// The rendering reason - /// The page html as a string when running in classic mode. In Pipeline mode the content is written directly to the HttpContext and an empty string is returned. + /// + /// In Pipeline mode the content is written directly to the HttpContext and an empty string is returned. + /// The IIS classic mode is no longer supported and will throw an exception. + /// public static string RenderPreview(IPage selectedPage, IList contents, RenderingReason renderingReason) { HttpContext ctx = HttpContext.Current; @@ -51,212 +49,27 @@ public static string RenderPreview(IPage selectedPage, IList has source "~/Composite/content/flow/FlowUi.aspx" and page is rendered from "~/Renderers/Page.aspx" - // The following line fixes style references processed by ASP.NET-s Control.ResolveClientUrl() - sb = sb.Replace("href=\"../", "href=\"../../../"); - - return sb.ToString(); - } - - return String.Empty; - } - - - /// - /// sigh - this fixes a fucked up issue, where previewing pages containing code writing to Session, - /// will breake all subsequent page previews regardless of content. Should you obtain the wisdom as - /// to what exactly is the trick here, I'd love to now. I will leave it as "well, this fix the issue - /// and pass testing. Hurray for Harry Potter and magic!". Oh how I loathe doing that :( - /// - /// the Http context that will be shared between master and child process - private static void AllowChildRequestSessionAccess(HttpContext ctx) - { - SessionIDManager manager = new SessionIDManager(); - string oldId = manager.GetSessionID(ctx); - string newId = manager.CreateSessionID(ctx); - bool isAdd = false, isRedir = false; - - manager.SaveSessionID(ctx, newId, out isRedir, out isAdd); - HttpApplication ctx2 = (HttpApplication)HttpContext.Current.ApplicationInstance; - HttpModuleCollection mods = ctx2.Modules; - SessionStateModule ssm = (SessionStateModule)mods.Get("Session"); - System.Reflection.FieldInfo[] fields = ssm.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance); - SessionStateStoreProviderBase store = null; - System.Reflection.FieldInfo rqIdField = null, rqLockIdField = null, rqStateNotFoundField = null; - foreach (System.Reflection.FieldInfo field in fields) - { - if (field.Name.Equals("_store")) store = (SessionStateStoreProviderBase)field.GetValue(ssm); - if (field.Name.Equals("_rqId")) rqIdField = field; - if (field.Name.Equals("_rqLockId")) rqLockIdField = field; - if (field.Name.Equals("_rqSessionStateNotFound")) rqStateNotFoundField = field; - } - object lockId = rqLockIdField.GetValue(ssm); - if ((lockId != null) && (oldId != null)) store.ReleaseItemExclusive(ctx, oldId, lockId); - rqStateNotFoundField.SetValue(ssm, true); - rqIdField.SetValue(ssm, newId); - } - - private class PreviewWorkerRequest : SimpleWorkerRequest - { - private string _callingUA; - private string _cookie; - private TextWriter _outputTextWriter; - - public PreviewWorkerRequest(string query, HttpContext callerContext, TextWriter output) - : base(@"Renderers\Page.aspx", query, output) - { - _callingUA = callerContext.Request.Headers["User-Agent"]; - _cookie = callerContext.Request.Headers["Cookie"]; - _outputTextWriter = output; - } - - public override string GetKnownRequestHeader(int index) - { - if (index == HttpWorkerRequest.HeaderUserAgent) - { - return _callingUA; - } - - if (index == HttpWorkerRequest.HeaderCookie) - { - return _cookie; - } - - return base.GetKnownRequestHeader(index); - } - - public override void SendResponseFromMemory(byte[] data, int length) - { - _outputTextWriter.Write(Encoding.UTF8.GetString(data,0, length)); - } - } - - - - private class FixLinksFilter : System.IO.Stream - { - private readonly System.IO.Stream _innerStream; - private System.IO.MemoryStream _ms = new System.IO.MemoryStream(); - - public FixLinksFilter(System.IO.Stream innerOuputStream) - { - _innerStream = innerOuputStream; - } - - public override bool CanRead - { - get { return false; } - } - - public override bool CanSeek - { - get { return false; } - } - - public override bool CanWrite - { - get { return true; } - } - - public override void Flush() - { - // DO NOTHING HERE - } - - public override long Length - { - get { throw new NotSupportedException(); } - } - - public override long Position - { - get - { - throw new NotSupportedException(); - } - set - { - throw new NotSupportedException(); - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); + throw new InvalidOperationException("IIS classic mode not supported"); } - public override long Seek(long offset, System.IO.SeekOrigin origin) + // The header trick here is to work around (what seems to be) a bug in .net 4.5, where preserveForm=false is ignored + // asp.net 4.5 request validation will see the 'page edit http post' data and start bitching. It really should not. + var headers = new System.Collections.Specialized.NameValueCollection { - throw new NotSupportedException(); - } + {"Content-Length", "0"} + }; - public override void SetLength(long value) + string cookieHeader = ctx.Request.Headers["Cookie"]; + if (!string.IsNullOrEmpty(cookieHeader)) { - throw new NotSupportedException(); + headers.Add("Cookie", cookieHeader); } - public override void Write(byte[] buffer, int offset, int count) - { - if (!_ms.CanWrite) - { - // Reopening stream if it was empty - _ms = new System.IO.MemoryStream(); - } - _ms.Write(buffer, offset, count); - } - - public override void Close() - { - // Checking if the stream was already closed - if (!_ms.CanSeek) - { - return; - } + ctx.Server.TransferRequest("~/Renderers/Page.aspx?" + query, false, "GET", headers); - _ms.Seek(0, System.IO.SeekOrigin.Begin); - - var bytes = _ms.ToArray(); - - string html = Encoding.UTF8.GetString(bytes); - - string newHtml = PageUrlHelper.ChangeRenderingPageUrlsToPublic(html); - - if (html != newHtml) - { - bytes = Encoding.UTF8.GetBytes(newHtml); - } - - _innerStream.Write(bytes, 0, bytes.Length); - - _innerStream.Close(); - _ms.Close(); - } + return String.Empty; } - } } From e91c22e9ce614e3acaf2b75d6f84e7c9e5fdb398 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Mon, 22 Oct 2018 16:21:49 +0300 Subject: [PATCH 35/51] Fix #370 [Console Search] IE11 & Edge: Clicking a link in the search results reloads the Console --- Website/Composite/console/access/utils.js | 14 ++++++++++++-- .../components/presentation/SearchResults.js | 17 +++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Website/Composite/console/access/utils.js b/Website/Composite/console/access/utils.js index b10c6fb529..1dee81a6d2 100644 --- a/Website/Composite/console/access/utils.js +++ b/Website/Composite/console/access/utils.js @@ -1,6 +1,16 @@ -export function getBaseUrl() { +export function getBaseUrl() { let baseUrlMatches = /^.*?(?=\/Composite\/)/gi.exec(location.pathname); - let baseUrl = baseUrlMatches == null ? "" : baseUrlMatches[0]; + let baseUrl = baseUrlMatches == null ? '' : baseUrlMatches[0]; return baseUrl; } + +export function handleLinkClick(e) { + if (top && top.Client && top.Client.isAnyExplorer) { + e.preventDefault(); + let link = e.target; + if (link.target == '_top' && link.href.indexOf('#') > 1) { + top.location.href = link.href; + } + } +} \ No newline at end of file diff --git a/Website/Composite/console/components/presentation/SearchResults.js b/Website/Composite/console/components/presentation/SearchResults.js index c8bc647053..591de09537 100644 --- a/Website/Composite/console/components/presentation/SearchResults.js +++ b/Website/Composite/console/components/presentation/SearchResults.js @@ -3,6 +3,7 @@ import styled, { css } from 'styled-components'; import colors from 'console/components/colors.js'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Icon from 'console/components/presentation/Icon.js'; +import { handleLinkClick } from 'console/access/utils.js'; export const ResultTable = styled.table` width: 100%; @@ -54,13 +55,13 @@ text-align: left; font-weight: normal; cursor: default; ${props => props.sortable ? - css` + css` cursor: pointer; &:hover { text-decoration: underline; }` : - '' -} + '' + } &:first-child { border-left: 1px solid ${colors.borderColor}; } @@ -84,19 +85,19 @@ const SearchResults = props => { () => { let searchQuery = props.searchQuery; if (searchQuery.get('sortBy') === col.get('fieldName')) { - searchQuery = searchQuery.set('sortInReverseOrder', ! searchQuery.get('sortInReverseOrder')); + searchQuery = searchQuery.set('sortInReverseOrder', !searchQuery.get('sortInReverseOrder')); } else { searchQuery = searchQuery.set('sortBy', col.get('fieldName')).set('sortInReverseOrder', false); } props.updateQuery(searchQuery); props.performSearch(searchQuery); } : - () => {}; + () => { }; return {col.get('label')} {props.searchQuery.get('sortBy') === col.get('fieldName') ? - : - null} + : + null} ; }).toArray()} @@ -106,7 +107,7 @@ const SearchResults = props => { {props.resultColumns.map(col => {col.get('fieldName') === props.urlColumn ? - {row.getIn(['values', col.get('fieldName')])} : + {row.getIn(['values', col.get('fieldName')])} : row.getIn(['values', col.get('fieldName')]) } ).toArray()} From 4a59d73e5063e4ff0897d9f0435e02b51c1d34a3 Mon Sep 17 00:00:00 2001 From: neexite Date: Mon, 22 Oct 2018 17:56:44 +0300 Subject: [PATCH 36/51] Fix #620 [Edge] Forms are broken on wide screens --- .../source/top/ui/bindings/fields/FieldsBinding.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Website/Composite/scripts/source/top/ui/bindings/fields/FieldsBinding.js b/Website/Composite/scripts/source/top/ui/bindings/fields/FieldsBinding.js index 9d17c2dc4e..e68319da35 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/fields/FieldsBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/fields/FieldsBinding.js @@ -76,6 +76,17 @@ FieldsBinding.prototype.onBindingInitialize = function () { if ( firstgroup != null ) { firstgroup.attachClassName ( FieldGroupBinding.CLASSNAME_FIRST ); } + + /** + * Limit width becouse Edge smooth non-breakable items in columns #620 + */ + if (Client.isEdge) { + var editopPage = this.getAncestorBindingByType(EditorPageBinding); + if (editopPage && this.bindingElement.childElementCount > 0) { + let columnWidth = 430; + this.bindingElement.style.maxWidth = (this.bindingElement.childElementCount + 1) * columnWidth - 1 + 'px'; + } + } } /** From ece7caa07884384126d19eff192ab7da099b6e8c Mon Sep 17 00:00:00 2001 From: neexite Date: Tue, 23 Oct 2018 10:04:25 +0300 Subject: [PATCH 37/51] Fix #318 Function are not visually selected when the page content gets selected --- Website/Frontend/Config/VisualEditor/Styles/core.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Website/Frontend/Config/VisualEditor/Styles/core.css b/Website/Frontend/Config/VisualEditor/Styles/core.css index 9504883cc6..c55cdbbff2 100644 --- a/Website/Frontend/Config/VisualEditor/Styles/core.css +++ b/Website/Frontend/Config/VisualEditor/Styles/core.css @@ -140,7 +140,7 @@ img.compositeFunctionWysiwygRepresentation.loaded.editHover { cursor: pointer; } -img.compositeFunctionWysiwygRepresentation::selection { +img.compositeFunctionWysiwygRepresentation.mceC1Focused::selection { background-color: transparent; color: #000; } From b22de84c1bab822c4e0b64c8a72bb023f8d6b0f3 Mon Sep 17 00:00:00 2001 From: neexite Date: Wed, 24 Oct 2018 10:09:25 +0300 Subject: [PATCH 38/51] fix js minifying --- .../scripts/source/top/ui/bindings/fields/FieldsBinding.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Website/Composite/scripts/source/top/ui/bindings/fields/FieldsBinding.js b/Website/Composite/scripts/source/top/ui/bindings/fields/FieldsBinding.js index e68319da35..309fa2f43d 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/fields/FieldsBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/fields/FieldsBinding.js @@ -83,7 +83,7 @@ FieldsBinding.prototype.onBindingInitialize = function () { if (Client.isEdge) { var editopPage = this.getAncestorBindingByType(EditorPageBinding); if (editopPage && this.bindingElement.childElementCount > 0) { - let columnWidth = 430; + var columnWidth = 430; this.bindingElement.style.maxWidth = (this.bindingElement.childElementCount + 1) * columnWidth - 1 + 'px'; } } From 8763abc877880a6974d1f9f1d376831134a2df0b Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 26 Oct 2018 10:22:16 +0200 Subject: [PATCH 39/51] Search index rebuild -> logging information about a published data item if it is missing from administrated scope --- .../Search/DocumentSources/DataTypeDocumentSource.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Composite/Search/DocumentSources/DataTypeDocumentSource.cs b/Composite/Search/DocumentSources/DataTypeDocumentSource.cs index 91cc2cb3cf..d56e1d63af 100644 --- a/Composite/Search/DocumentSources/DataTypeDocumentSource.cs +++ b/Composite/Search/DocumentSources/DataTypeDocumentSource.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -195,7 +195,6 @@ private SearchDocument FromData(IData data, CultureInfo culture) var entityToken = GetConsoleEntityToken(data); if (entityToken == null) { - Log.LogWarning(LogTitle, $"Failed to obtain an entity token for a data item of type '{data.DataSourceId.InterfaceType}'"); return null; } @@ -212,7 +211,13 @@ private EntityToken GetConsoleEntityToken(IData data) } var administratedData = DataFacade.GetDataFromOtherScope(data, DataScopeIdentifier.Administrated).FirstOrDefault(); - return administratedData?.GetDataEntityToken(); + if (administratedData == null) + { + Log.LogWarning(LogTitle, $"The following data item exists in published scope, but doesn't exist in unpublished scope '{data.DataSourceId.Serialize()}'."); + return null; + } + + return administratedData.GetDataEntityToken(); } private string GetDocumentId(IData data) From 3fab64c91638973852ecf86295b65b19a8c0a6d1 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 12 Nov 2018 14:31:55 +0100 Subject: [PATCH 40/51] C1 console can be loaded even if one of attached element providers fails to load or missing --- Composite/C1Console/Elements/ElementFacade.cs | 23 +++++++++++++++---- .../VirtualElementProvider.cs | 10 ++------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Composite/C1Console/Elements/ElementFacade.cs b/Composite/C1Console/Elements/ElementFacade.cs index f3c15785ef..568da88408 100644 --- a/Composite/C1Console/Elements/ElementFacade.cs +++ b/Composite/C1Console/Elements/ElementFacade.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Xml; @@ -10,6 +10,7 @@ using Composite.C1Console.Forms.DataServices; using Composite.C1Console.Forms.Flows; using Composite.C1Console.Security; +using Composite.Core; using Composite.Core.Logging; using Composite.Core.Instrumentation; using Composite.Plugins.Elements.ElementProviders.VirtualElementProvider; @@ -280,14 +281,26 @@ private static IEnumerable GetRoots(string providerName, SearchToken se if (providerName == null) throw new ArgumentNullException("providerName"); IEnumerable roots; - if (!useForeign || !ElementProviderPluginFacade.IsLocaleAwareElementProvider(providerName)) + + try { - roots = ElementProviderPluginFacade.GetRoots(providerName, searchToken).ToList(); + if (!useForeign || !ElementProviderPluginFacade.IsLocaleAwareElementProvider(providerName)) + { + roots = ElementProviderPluginFacade.GetRoots(providerName, searchToken).ToList(); + } + else + { + roots = ElementProviderPluginFacade.GetForeignRoots(providerName, searchToken).ToList(); + } } - else + catch (Exception ex) when (providerName != ElementProviderRegistry.RootElementProviderName) { - roots = ElementProviderPluginFacade.GetForeignRoots(providerName, searchToken).ToList(); + Log.LogError(nameof(ElementFacade), $"Failed to get root elements for element provider '{providerName}'"); + Log.LogError(nameof(ElementFacade), ex); + + return Enumerable.Empty(); } + if (performSecurityCheck) { diff --git a/Composite/Plugins/Elements/ElementProviders/VirtualElementProvider/VirtualElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/VirtualElementProvider/VirtualElementProvider.cs index db3382af1d..c6d98e78ab 100644 --- a/Composite/Plugins/Elements/ElementProviders/VirtualElementProvider/VirtualElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/VirtualElementProvider/VirtualElementProvider.cs @@ -562,14 +562,8 @@ private Element CreateElement(SimpleVirtualElement simpleElementNode) }; collectProviders(simpleElementNode.Elements); - - foreach (var attachedProvider in attachedProviders) - { - if (ElementFacade.ContainsLocalizedData(new ElementProviderHandle(attachedProvider.ProviderName))) - { - element.IsLocaleAware = true; - } - } + element.IsLocaleAware = attachedProviders.Any(provider => + ElementFacade.ContainsLocalizedData(new ElementProviderHandle(provider.ProviderName))); if (element.VisualData.HasChildren) From 4162c4b1ad00155eb880633f4db97db2e79ae6cd Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 20 Nov 2018 15:22:47 +0100 Subject: [PATCH 41/51] Optimizing code that upgrades entity tokens, which otherwise can take up to an hour of execution time. --- .../LegacySerializedEntityTokenUpgrader.cs | 89 ++++++++++++------- .../C1Console/Tasks/TaskManagerFacadeImpl.cs | 34 +++++-- 2 files changed, 80 insertions(+), 43 deletions(-) diff --git a/Composite/C1Console/Security/Compatibility/LegacySerializedEntityTokenUpgrader.cs b/Composite/C1Console/Security/Compatibility/LegacySerializedEntityTokenUpgrader.cs index 6d641245d2..197ddeb7c3 100644 --- a/Composite/C1Console/Security/Compatibility/LegacySerializedEntityTokenUpgrader.cs +++ b/Composite/C1Console/Security/Compatibility/LegacySerializedEntityTokenUpgrader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -24,6 +24,8 @@ public static class LegacySerializedEntityTokenUpgrader static readonly XName _docRoot = "UpgradeSettings"; private static ILog _log; + private const int MaxErrorMessagesPerType = 1000; + /// /// See class description - this allow us to run on startup /// @@ -65,9 +67,9 @@ private static bool ShouldRun { var _xConfig = GetConfigElement(); return true == (bool)_xConfig.Attribute("enabled") && - (false == (bool)_xConfig.Attribute("completed") || - true == (bool)_xConfig.Attribute("force") || - (DateTime)_xConfig.Attribute("last-run") - (DateTime)_xConfig.Attribute("inception") < TimeSpan.FromMinutes(5)); + (false == (bool)_xConfig.Attribute("completed") || + true == (bool)_xConfig.Attribute("force") || + (DateTime)_xConfig.Attribute("last-run") - (DateTime)_xConfig.Attribute("inception") < TimeSpan.FromMinutes(5)); } } @@ -130,10 +132,14 @@ private static void UpgradeStoredData() propertiesToUpdate.Add(tokenProperty); } - using (var dc = new DataConnection(PublicationScope.Unpublished)) + using (new DataConnection(PublicationScope.Unpublished)) { var allRows = DataFacade.GetData(dataType).ToDataList(); + var toUpdate = new List(); + + int errors = 0, updated = 0; + foreach (var rowItem in allRows) { bool rowChange = false; @@ -142,48 +148,62 @@ private static void UpgradeStoredData() { string token = tokenProperty.GetValue(rowItem) as string; - if (tokenProperty.Name.Contains(_ET)) + try { - try - { - var entityToken = EntityTokenSerializer.Deserialize(token); - var tokenReserialized = EntityTokenSerializer.Serialize(entityToken); + string tokenReserialized; - if (tokenReserialized != token) - { - tokenProperty.SetValue(rowItem, tokenReserialized); - rowChange = true; - } - } - catch (Exception ex) + if (tokenProperty.Name.Contains(_ET)) { - _log.LogError(LogTitle, $"Failed to upgrade old token '{token}' from data type '{dataType.FullName}' as EntityToken.\n{ex}"); + var entityToken = EntityTokenSerializer.Deserialize(token); + tokenReserialized = EntityTokenSerializer.Serialize(entityToken); } - } - - if (tokenProperty.Name.Contains(_DSI)) - { - try + else if (tokenProperty.Name.Contains(_DSI)) { token = EnsureValidDataSourceId(token); var dataSourceId = DataSourceId.Deserialize(token); - var dataSourceIdReserialized = dataSourceId.Serialize(); + tokenReserialized = dataSourceId.Serialize(); + } + else + { + throw new InvalidOperationException("This line should not be reachable"); + } - if (dataSourceIdReserialized != token) - { - tokenProperty.SetValue(rowItem, dataSourceIdReserialized); - rowChange = true; - } + if (tokenReserialized != token) + { + tokenProperty.SetValue(rowItem, tokenReserialized); + rowChange = true; } - catch (Exception ex) + } + catch (Exception ex) + { + errors++; + if (errors <= MaxErrorMessagesPerType) { - _log.LogError(LogTitle, $"Failed to upgrade old token '{token}' from data type '{dataType.FullName}' as DataSourceId.\n{ex}"); + _log.LogError(LogTitle, $"Failed to upgrade old token '{token}' from data type '{dataType.FullName}' as EntityToken.\n{ex}"); } } + } + + if (rowChange) + { + updated++; + toUpdate.Add(rowItem); - if (rowChange) DataFacade.Update(rowItem, true, false, false); + if (toUpdate.Count >= 1000) + { + DataFacade.Update(toUpdate, true, false, false); + toUpdate.Clear(); + } } } + + if (toUpdate.Count > 0) + { + DataFacade.Update(toUpdate, true, false, false); + toUpdate.Clear(); + } + + _log.LogInformation(LogTitle, $"Finished updating serialized tokens for data type '{dataType.FullName}'. Rows: {allRows.Count}, Updated: {updated}, Errors: {errors}"); } } } @@ -199,8 +219,9 @@ private static string EnsureValidDataSourceId(string token) token = token.Replace("'_dataIdType_", String.Format(",\\ VersionId=\\'{0}\\''_dataIdType_", pageId)); } } - catch(Exception) { - // if we have an issue, caller will act + catch (Exception) + { + // if we have an issue, caller will act } return token; diff --git a/Composite/C1Console/Tasks/TaskManagerFacadeImpl.cs b/Composite/C1Console/Tasks/TaskManagerFacadeImpl.cs index d20e2f16c6..c7be8dfd23 100644 --- a/Composite/C1Console/Tasks/TaskManagerFacadeImpl.cs +++ b/Composite/C1Console/Tasks/TaskManagerFacadeImpl.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Composite.C1Console.Actions; @@ -9,7 +9,6 @@ using Composite.Data.Types; using Composite.Core.Threading; using Composite.Core.Types; -using Composite.Core.Logging; namespace Composite.C1Console.Tasks @@ -107,24 +106,41 @@ private void LoadTasks() using (ThreadDataManager.EnsureInitialize()) { IEnumerable taskItems = DataFacade.GetData().Evaluate(); + + var toDelete = new List(); + foreach (ITaskItem taskItem in taskItems) { Type type = TypeManager.TryGetType(taskItem.TaskManagerType); if (type == null) { - LoggingService.LogWarning("TaskManagerFacade", string.Format("Could not get the type '{0}'", taskItem.TaskManagerType)); - LoggingService.LogWarning("TaskManagerFacade", string.Format("Removing task item with id '{0}'. The Task Manager Type can not be found.", taskItem.TaskId)); - DataFacade.Delete(taskItem); + Log.LogWarning(nameof(TaskManagerFacade), + $"Removing task item with id '{taskItem.TaskId}'. The Task Manager Type '{taskItem.TaskManagerType}' can not be found."); + + toDelete.Add(taskItem); + + if (toDelete.Count >= 100) + { + DataFacade.Delete(toDelete); + toDelete.Clear(); + } continue; } - Task task = new Task(taskItem.TaskId, type); - task.StartTime = taskItem.StartTime; - task.FlowToken = taskItem.SerializedFlowToken; + Task task = new Task(taskItem.TaskId, type) + { + StartTime = taskItem.StartTime, + FlowToken = taskItem.SerializedFlowToken + }; _tasks.Add(task); } + + if (toDelete.Count > 0) + { + DataFacade.Delete(toDelete); + } } - } + } } } From 07d7df47b2be66735190844521c813ad6ef0d613 Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 27 Nov 2018 11:42:41 +0100 Subject: [PATCH 42/51] Changing version to 6.6 --- Composite/Properties/SharedAssemblyInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Composite/Properties/SharedAssemblyInfo.cs b/Composite/Properties/SharedAssemblyInfo.cs index e58902ca8d..9d8c21ed56 100644 --- a/Composite/Properties/SharedAssemblyInfo.cs +++ b/Composite/Properties/SharedAssemblyInfo.cs @@ -2,9 +2,9 @@ // General Information about the assemblies Composite and Composite.Workflows #if !InternalBuild -[assembly: AssemblyTitle("C1 CMS 6.5")] +[assembly: AssemblyTitle("C1 CMS 6.6")] #else -[assembly: AssemblyTitle("C1 CMS 6.5 (Internal Build)")] +[assembly: AssemblyTitle("C1 CMS 6.6 (Internal Build)")] #endif [assembly: AssemblyCompany("Orckestra Inc")] @@ -13,4 +13,4 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("6.5.*")] +[assembly: AssemblyVersion("6.6.*")] From b2827bc922eb33f40f74944fbcff432a0c7cb0ac Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 27 Nov 2018 13:44:16 +0100 Subject: [PATCH 43/51] Improved reset-installation.bat (dev/test artifact for fast site reset) --- Website/App_Data/Composite/reset-installation.bat | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Website/App_Data/Composite/reset-installation.bat b/Website/App_Data/Composite/reset-installation.bat index 82d59e9ad2..85016c1947 100644 --- a/Website/App_Data/Composite/reset-installation.bat +++ b/Website/App_Data/Composite/reset-installation.bat @@ -35,14 +35,13 @@ rd Azure /S /Q rd InlineCSharpFunctions /S /Q md InlineCSharpFunctions - -copy TreeDefinitions\PageType.xml TreeDefinitions\PageType.xml.backup /y -:: del TreeDefinitions\*.xml -copy TreeDefinitions\PageType.xml.backup TreeDefinitions\PageType.xml /y -del TreeDefinitions\PageType.xml.backup - -:: copy ..\..\..\AutoInstallPackages\Develop\BaseConfigurationDkUs.zip AutoInstallPackages /y - +ren TreeDefinitions\PageType.xml PageType.xml.backup +ren TreeDefinitions\ServerLog.xml ServerLog.xml.backup +ren TreeDefinitions\UrlConfiguration.xml UrlConfiguration.xml.backup +del TreeDefinitions\*.xml /Q +ren TreeDefinitions\PageType.xml.backup PageType.xml +ren TreeDefinitions\ServerLog.xml.backup ServerLog.xml +ren TreeDefinitions\UrlConfiguration.xml.backup UrlConfiguration.xml :: Basic cleanup rd ..\..\Frontend\Composite /S /Q From 7ba27888cde7e28ef687d1b185abc61980d63fba Mon Sep 17 00:00:00 2001 From: mawtex Date: Wed, 28 Nov 2018 15:07:42 +0100 Subject: [PATCH 44/51] StartupHandlersCache.xml - problems updating this file will no longer prevent a app start. For deployments running on a shared disk, "used by another process" may happen and (re)updating this file is not critical for functionality, exception is not needed. --- .../AttributeBasedApplicationStartupHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Composite/Plugins/Application/ApplicationStartupHandlers/AttributeBasedApplicationStartupHandler/AttributeBasedApplicationStartupHandler.cs b/Composite/Plugins/Application/ApplicationStartupHandlers/AttributeBasedApplicationStartupHandler/AttributeBasedApplicationStartupHandler.cs index e98d96fd08..54af94ad7a 100644 --- a/Composite/Plugins/Application/ApplicationStartupHandlers/AttributeBasedApplicationStartupHandler/AttributeBasedApplicationStartupHandler.cs +++ b/Composite/Plugins/Application/ApplicationStartupHandlers/AttributeBasedApplicationStartupHandler/AttributeBasedApplicationStartupHandler.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -410,9 +410,9 @@ private void SaveTypesCache(List cachedTypesInfo) } } } - catch (UnauthorizedAccessException) + catch (Exception) { - Log.LogWarning(LogTitle, $"Failed to open file '{CacheFilePath}'"); + Log.LogWarning(LogTitle, $"Failed to open file '{CacheFilePath}' for writing - this may lead to slower start up times, if this issue persist. In that case, check that this file is accessible to the web application for writes."); } } From 7710fc9c37ed41db5598f7d76b66fc33d51ea4c4 Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 29 Nov 2018 15:17:04 +0100 Subject: [PATCH 45/51] Not creating directories (for file data providers) up front. --- .../FileSystemDataProvider/FileSystemDataProvider.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Composite/Plugins/Data/DataProviders/FileSystemDataProvider/FileSystemDataProvider.cs b/Composite/Plugins/Data/DataProviders/FileSystemDataProvider/FileSystemDataProvider.cs index 5d89c97bf7..e1ae39b6d8 100644 --- a/Composite/Plugins/Data/DataProviders/FileSystemDataProvider/FileSystemDataProvider.cs +++ b/Composite/Plugins/Data/DataProviders/FileSystemDataProvider/FileSystemDataProvider.cs @@ -305,18 +305,6 @@ public IDataProvider Assemble(IBuilderContext context, DataProviderData objectCo if (configuration == null) throw new ArgumentException("Expected configuration to be of type FileSystemDataProviderData", "objectConfiguration"); string resolvedRootDirectory = PathUtil.Resolve(configuration.RootDirectory); - if (C1Directory.Exists(resolvedRootDirectory) == false) - { - try - { - C1Directory.CreateDirectory(resolvedRootDirectory); - } - catch (Exception ex) - { - string directoryNotFoundMsg = string.Format("The directory '{0}' was not found and could not be created.", configuration.RootDirectory); - throw new ConfigurationErrorsException(directoryNotFoundMsg, ex, configuration.ElementInformation.Source, configuration.ElementInformation.LineNumber); - } - } if (typeof(IFile).IsAssignableFrom(configuration.FileInterfaceType) == false) { From 88f82baa6f4535f7c6d20feed9fa11d6fb76ee91 Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 29 Nov 2018 15:37:41 +0100 Subject: [PATCH 46/51] Initislization of console artifacts moved out of general system initialization, now execute when a validated user access console. --- ...AdministrativeDataScopeSetterHttpModule.cs | 26 +++++++++++-- Composite/GlobalInitializerFacade.cs | 38 ------------------- 2 files changed, 23 insertions(+), 41 deletions(-) diff --git a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs index aa41875d99..4a99e852ee 100644 --- a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs +++ b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs @@ -1,15 +1,20 @@ -using System; +using System; using System.Web; using Composite.C1Console.Security; -using Composite.Data; using Composite.C1Console.Users; +using Composite.C1Console.Actions; +using Composite.C1Console.Events; +using Composite.C1Console.Elements.Foundation; using Composite.Core.Configuration; - +using Composite.Data; namespace Composite.Core.WebClient.HttpModules { internal class AdministrativeDataScopeSetterHttpModule : IHttpModule { + private static bool _consoleArtifactsInitialized = false; + private static object _consoleArtifactsInitializeLock = new object(); + public void Init(HttpApplication context) { if (!SystemSetupFacade.IsSystemFirstTimeInitialized) @@ -38,6 +43,21 @@ public void AuthorizeRequest(object sender, EventArgs e) if (adminRootRequest && UserValidationFacade.IsLoggedIn()) { _dataScope = new DataScope(DataScopeIdentifier.Administrated, UserSettings.ActiveLocaleCultureInfo); + + if (!_consoleArtifactsInitialized) + { + lock(_consoleArtifactsInitializeLock) + { + if (!_consoleArtifactsInitialized && !SystemSetupFacade.SetupIsRunning) + { + _consoleArtifactsInitialized = true; + HookingFacade.EnsureInitialization(); + FlowControllerFacade.Initialize(); + ConsoleFacade.Initialize(); + ElementProviderLoader.LoadAllProviders(); + } + } + } } } diff --git a/Composite/GlobalInitializerFacade.cs b/Composite/GlobalInitializerFacade.cs index 37ca291adf..7def1b0e4c 100644 --- a/Composite/GlobalInitializerFacade.cs +++ b/Composite/GlobalInitializerFacade.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Web; using System.Web.Hosting; -using Composite.C1Console.Actions; using Composite.C1Console.Events; using Composite.C1Console.Security; using Composite.C1Console.Workflow; @@ -25,7 +24,6 @@ using Composite.Data.Foundation; using Composite.Data.ProcessControlled; using Composite.Functions.Foundation; -using Composite.C1Console.Elements.Foundation; using Composite.Data.Foundation.PluginFacades; using Composite.Plugins.Data.DataProviders.MSSqlServerDataProvider.Sql; @@ -268,32 +266,12 @@ private static void DoInitialize() } - if (!RuntimeInformation.IsUnittest) - { - using (new LogExecutionTime(LogTitle, "Initializing flow system")) - { - FlowControllerFacade.Initialize(); - } - - using (new LogExecutionTime(LogTitle, "Initializing console system")) - { - ConsoleFacade.Initialize(); - } - } - - using (new LogExecutionTime(LogTitle, "Auto installing packages")) { DoAutoInstallPackages(); } - using (new LogExecutionTime(LogTitle, "Loading element providers")) - { - ElementProviderLoader.LoadAllProviders(); - } - - int executionTime = Environment.TickCount - startTime; Log.LogVerbose(LogTitle, $"Done initializing of the system core. ({executionTime} ms)"); @@ -357,22 +335,6 @@ internal static void ReinitializeTheSystem(RunInWriterLockScopeDelegate runInWri InitializeTheSystem(); - if (!SystemSetupFacade.SetupIsRunning) - { - // Updating "hooks" either in the same thread, or in another - if (initializeHooksInTheSameThread) - { - object threadStartParameter = new KeyValuePair(TimeSpan.Zero, new StackTrace()); - EnsureHookingFacade(threadStartParameter); - } - else - { - _hookingFacadeThread = new Thread(EnsureHookingFacade) {Name = "EnsureHookingFacade"}; - _hookingFacadeThread.Start(new KeyValuePair(TimeSpan.FromSeconds(1), new StackTrace())); - } - } - - IsReinitializingTheSystem = false; } } From 3e87087e0e9e64fb8d6ca91f5c6ded6081c28456 Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 30 Nov 2018 10:59:51 +0100 Subject: [PATCH 47/51] Console initialization, ensuring propper mutex --- .../HttpModules/AdministrativeDataScopeSetterHttpModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs index 4a99e852ee..1fb49ab4aa 100644 --- a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs +++ b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs @@ -50,11 +50,11 @@ public void AuthorizeRequest(object sender, EventArgs e) { if (!_consoleArtifactsInitialized && !SystemSetupFacade.SetupIsRunning) { - _consoleArtifactsInitialized = true; HookingFacade.EnsureInitialization(); FlowControllerFacade.Initialize(); ConsoleFacade.Initialize(); ElementProviderLoader.LoadAllProviders(); + _consoleArtifactsInitialized = true; } } } From 53ad13248d97a763489017da39866eb043ec99be Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 30 Nov 2018 12:46:39 +0100 Subject: [PATCH 48/51] Making 'missing string' log details verbose (was warning/error). --- Composite/Core/ResourceSystem/StringResourceSystemFacade.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Composite/Core/ResourceSystem/StringResourceSystemFacade.cs b/Composite/Core/ResourceSystem/StringResourceSystemFacade.cs index eaeb7a4238..e3eb22c38b 100644 --- a/Composite/Core/ResourceSystem/StringResourceSystemFacade.cs +++ b/Composite/Core/ResourceSystem/StringResourceSystemFacade.cs @@ -79,13 +79,13 @@ public static string GetString(string section, string stringName, bool throwOnEr if (!ResourceProviderPluginFacade.LocalizationSectionDefined(section)) { - Log.LogCritical(LogTitle, "Localization section not defined '{0}:{1}'".FormatWith(section, stringName)); + Log.LogVerbose(LogTitle, "Localization section not defined '{0}:{1}'".FormatWith(section, stringName)); return Error_SectionNotDefined; } - Log.LogWarning(LogTitle, "Localization string not defined '{0}:{1}'".FormatWith(section, stringName)); + Log.LogVerbose(LogTitle, "Localization string not defined '{0}:{1}'".FormatWith(section, stringName)); return Error_StringNotDefined; } @@ -124,7 +124,7 @@ public static List GetLocalization(string providerName) if(translations == null) { - Log.LogCritical(LogTitle, "Missing localization section: '{0}'".FormatWith(providerName)); + Log.LogVerbose(LogTitle, "Missing localization section: '{0}'".FormatWith(providerName)); return new List(); } From ebefdaac4d963d8ed0be849ab9dbbb933d644e2e Mon Sep 17 00:00:00 2001 From: mawtex Date: Fri, 30 Nov 2018 17:24:54 +0100 Subject: [PATCH 49/51] System.Diagnostics.Trace bridge added as a C1 log listener - will replicate log data onto standard Trace. See Composite.config file change in this commit for how to enable. --- Composite/Composite.csproj | 7 ++- .../SystemDiagnosticsTraceBridge.cs | 63 +++++++++++++++++++ .../Composite/DebugBuild.Composite.config | 10 +-- .../Composite/ReleaseBuild.Composite.config | 8 ++- ...seBuild.Composite.config.changeHistory.txt | 8 ++- 5 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 Composite/Plugins/Logging/LogTraceListeners/SystemDiagnosticsTrace/SystemDiagnosticsTraceBridge.cs diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 980f8a9175..c17780133c 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -1,4 +1,4 @@ - + Debug @@ -261,6 +261,7 @@ + @@ -2686,8 +2687,8 @@ - - + + $([System.IO.File]::ReadAllText("$(GitBranchFile)").Trim()) $([System.IO.File]::ReadAllText("$(GitCommitHashFile)").Trim()) diff --git a/Composite/Plugins/Logging/LogTraceListeners/SystemDiagnosticsTrace/SystemDiagnosticsTraceBridge.cs b/Composite/Plugins/Logging/LogTraceListeners/SystemDiagnosticsTrace/SystemDiagnosticsTraceBridge.cs new file mode 100644 index 0000000000..0df81ea8eb --- /dev/null +++ b/Composite/Plugins/Logging/LogTraceListeners/SystemDiagnosticsTrace/SystemDiagnosticsTraceBridge.cs @@ -0,0 +1,63 @@ +using System; +using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; +using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration; +using Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners; + +namespace Composite.Plugins.Logging.LogTraceListeners.SystemDiagnosticsTrace +{ + [ConfigurationElementType(typeof(CustomTraceListenerData))] + internal sealed class SystemDiagnosticsTraceBridge : CustomTraceListener + { + public SystemDiagnosticsTraceBridge() + { + } + + public SystemDiagnosticsTraceBridge(string initializeData) + { + } + + public override void TraceData(System.Diagnostics.TraceEventCache eventCache, string source, System.Diagnostics.TraceEventType eventType, int id, object data) + { + var logEntry = (Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry)data; + + string title = logEntry.Title; + title = title.Substring(title.IndexOf(')') + 2); // Removing ({AppDomainId} - {ThreadId}}) prefix. + if (title.StartsWith("RGB(")) + { + string displayOptions = title.Substring(0, title.IndexOf(')') + 1); + title = title.Substring(displayOptions.Length); + } + + string message = String.Format("[{0}] {1}", title, logEntry.Message); + + switch (logEntry.Severity) + { + case System.Diagnostics.TraceEventType.Critical: + case System.Diagnostics.TraceEventType.Error: + System.Diagnostics.Trace.TraceError(message); + break; + case System.Diagnostics.TraceEventType.Warning: + System.Diagnostics.Trace.TraceWarning(message); + break; + case System.Diagnostics.TraceEventType.Information: + System.Diagnostics.Trace.TraceInformation(message); + break; + default: + System.Diagnostics.Trace.WriteLine(message); + break; + } + + System.Diagnostics.Trace.Flush(); + } + + public override void Write(string message) + { + System.Diagnostics.Trace.Write(message); + } + + public override void WriteLine(string message) + { + System.Diagnostics.Trace.WriteLine(message); + } + } +} diff --git a/Website/App_Data/Composite/DebugBuild.Composite.config b/Website/App_Data/Composite/DebugBuild.Composite.config index f98c37d027..b444bb16eb 100644 --- a/Website/App_Data/Composite/DebugBuild.Composite.config +++ b/Website/App_Data/Composite/DebugBuild.Composite.config @@ -1,4 +1,4 @@ - +
@@ -478,8 +478,9 @@ - - + + + @@ -490,7 +491,8 @@ - + + diff --git a/Website/App_Data/Composite/ReleaseBuild.Composite.config b/Website/App_Data/Composite/ReleaseBuild.Composite.config index 12ff4cf345..112f99a63d 100644 --- a/Website/App_Data/Composite/ReleaseBuild.Composite.config +++ b/Website/App_Data/Composite/ReleaseBuild.Composite.config @@ -472,8 +472,9 @@ - - + + + @@ -485,7 +486,8 @@ - + + diff --git a/Website/App_Data/Composite/ReleaseBuild.Composite.config.changeHistory.txt b/Website/App_Data/Composite/ReleaseBuild.Composite.config.changeHistory.txt index 3018c58ee6..cce2ab8481 100644 --- a/Website/App_Data/Composite/ReleaseBuild.Composite.config.changeHistory.txt +++ b/Website/App_Data/Composite/ReleaseBuild.Composite.config.changeHistory.txt @@ -1,4 +1,4 @@ -Changes since released 1.0.3070.24011 (Nightly build 20080528.5) +Changes since released 1.0.3070.24011 (Nightly build 20080528.5) - added to std. Form UI Markup. - Composite.Core.Application.Plugins.ApplicationOnlineHandlerConfiguration added - added to std. Form UI Markup. @@ -166,4 +166,8 @@ Changes in 6.0 or later - \ No newline at end of file + + + Changes in 6.6 or later: + -Added to loggingConfiguration/listeners + -Added to loggingConfiguration/specialSources/allEvents/listeners \ No newline at end of file From c71a3ce92495064a6e1cc473b9265fc33936b9b0 Mon Sep 17 00:00:00 2001 From: mawtex Date: Mon, 3 Dec 2018 15:16:36 +0100 Subject: [PATCH 50/51] Adding trace flag to release project. --- Composite/Composite.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index c17780133c..794277cddf 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -57,8 +57,7 @@ pdbonly true bin\Release\ - - + TRACE prompt 4 From 38935d9bbd1eff468a26b5fab500f834060f96d2 Mon Sep 17 00:00:00 2001 From: mawtex Date: Tue, 4 Dec 2018 14:49:03 +0100 Subject: [PATCH 51/51] Fix #621 and fix #339 - at least this change should eliminate situation where rendering target pages (to register Dynamic Data Url Mappers) may fail due to target page URL containing /c1mode(relative). This area is still not perfect - this should be an in proc process to ensure it will execute in the same process (going via http request may hit another server or be blocked). --- Composite/Data/PageRenderingHistory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/Data/PageRenderingHistory.cs b/Composite/Data/PageRenderingHistory.cs index fa403d928a..1bd20a0686 100644 --- a/Composite/Data/PageRenderingHistory.cs +++ b/Composite/Data/PageRenderingHistory.cs @@ -129,7 +129,7 @@ private static void RenderPage(IPage page) return; } - var urlSpace = new UrlSpace(context) { ForceRelativeUrls = true }; + var urlSpace = new UrlSpace(context) { ForceRelativeUrls = false }; var url = PageUrls.BuildUrl(page, UrlKind.Public, urlSpace) ?? PageUrls.BuildUrl(page, UrlKind.Renderer, urlSpace);