From 7d2910866c1a63d48c1d588890d487785fabcdc7 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 16 Jun 2017 11:16:40 +0200 Subject: [PATCH 01/38] Adding a page rendering http handler that does not rely on building a WebForms control tree --- Composite/Composite.csproj | 1 + Composite/Core/PageTemplates/IPageRenderer.cs | 20 +++- .../Core/Routing/Pages/C1PageRouteHander.cs | 19 ++- .../Core/Routing/Pages/CmsPageHttpHandler.cs | 110 ++++++++++++++++++ .../PageTemplates/Razor/RazorPageRenderer.cs | 2 +- 5 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 Composite/Core/Routing/Pages/CmsPageHttpHandler.cs diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 7a111b4d12..9008ad24a6 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -252,6 +252,7 @@ + diff --git a/Composite/Core/PageTemplates/IPageRenderer.cs b/Composite/Core/PageTemplates/IPageRenderer.cs index 4b2f5fb846..eeebe6fc03 100644 --- a/Composite/Core/PageTemplates/IPageRenderer.cs +++ b/Composite/Core/PageTemplates/IPageRenderer.cs @@ -1,4 +1,7 @@ -namespace Composite.Core.PageTemplates +using System.Xml.Linq; +using Composite.Functions; + +namespace Composite.Core.PageTemplates { /// /// This class is responsible for rendering the provided job onto the provided asp.net web forms page. @@ -13,4 +16,19 @@ public interface IPageRenderer /// The render job. void AttachToPage(System.Web.UI.Page renderTaget, PageContentToRender contentToRender); } + + /// + /// A page renderer that does not rely of request being handled by ASP.NET Web Forms + /// and does not support UserControl functions + /// + public interface ISlimPageRenderer : IPageRenderer + { + /// + /// Rendering the content into an . + /// + /// The render job. + /// The function context container. + XDocument Render(PageContentToRender contentToRender, + FunctionContextContainer functionContextContainer); + } } diff --git a/Composite/Core/Routing/Pages/C1PageRouteHander.cs b/Composite/Core/Routing/Pages/C1PageRouteHander.cs index 949233e213..5c842df1cb 100644 --- a/Composite/Core/Routing/Pages/C1PageRouteHander.cs +++ b/Composite/Core/Routing/Pages/C1PageRouteHander.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Collections.Concurrent; using System.Linq; using System.Web; using System.Web.Compilation; @@ -10,6 +11,8 @@ using System.Xml.Linq; using Composite.Core.Extensions; using Composite.Core.Linq; +using Composite.Core.PageTemplates; + namespace Composite.Core.Routing.Pages { @@ -51,7 +54,7 @@ static C1PageRouteHandler() _handlerType = Type.GetType(typeAttr.Value); if(_handlerType == null) { - Log.LogError(typeof(C1PageRouteHandler).Name, "Failed to load type '{0}'", typeAttr.Value); + Log.LogError(typeof(C1PageRouteHandler).Name, $"Failed to load type '{typeAttr.Value}'"); } } } @@ -90,6 +93,11 @@ public IHttpHandler GetHttpHandler(RequestContext requestContext) context.Response.Cache.SetCacheability(HttpCacheability.NoCache); } + if (IsSlimPageRenderer(_pageUrlData.GetPage().TemplateId)) + { + return new CmsPageHttpHandler(); + } + if (_handlerType != null) { return (IHttpHandler)Activator.CreateInstance(_handlerType); @@ -97,5 +105,14 @@ public IHttpHandler GetHttpHandler(RequestContext requestContext) return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath("~/Renderers/Page.aspx", typeof(Page)); } + + private static readonly ConcurrentDictionary _pageRendererTypCache + = new ConcurrentDictionary(); + + private bool IsSlimPageRenderer(Guid pageTemplate) + { + return _pageRendererTypCache.GetOrAdd(pageTemplate, + templateId => PageTemplateFacade.BuildPageRenderer(templateId) is ISlimPageRenderer); + } } } diff --git a/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs b/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs new file mode 100644 index 0000000000..3c38a29565 --- /dev/null +++ b/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs @@ -0,0 +1,110 @@ +using System; +using System.Web; +using System.Web.UI; +using System.Xml.Linq; +using Composite.Core.Configuration; +using Composite.Core.Instrumentation; +using Composite.Core.PageTemplates; +using Composite.Core.WebClient.Renderings; +using Composite.Core.WebClient.Renderings.Page; +using Composite.Core.Xml; + +namespace Composite.Core.Routing.Pages +{ + internal class CmsPageHttpHandler: IHttpHandler + { + private const string CacheProfileName = "C1Page"; + + public void ProcessRequest(HttpContext context) + { + InitializeFullPageCaching(context); + + using (var renderingContext = RenderingContext.InitializeFromHttpContext()) + { + if (renderingContext.RunResponseHandlers()) + { + return; + } + + var renderer = PageTemplateFacade.BuildPageRenderer(renderingContext.Page.TemplateId); + + var slimRenderer = (ISlimPageRenderer) renderer; + + var functionContext = PageRenderer.GetPageRenderFunctionContextContainer(); + + XDocument document; + + using (Profiler.Measure($"{nameof(ISlimPageRenderer)}.Render")) + { + document = slimRenderer.Render(renderingContext.PageContentToRender, functionContext); + } + + PageRenderer.ProcessPageDocument(document, functionContext, renderingContext.Page); + + string xhtml; + if (document.Root.Name == RenderingElementNames.Html) + { + var xhtmlDocument = new XhtmlDocument(document); + + PageRenderer.ProcessXhtmlDocument(xhtmlDocument, renderingContext.Page); + + xhtml = xhtmlDocument.ToString(); + } + else + { + xhtml = document.ToString(); + } + + if (renderingContext.PreRenderRedirectCheck()) + { + return; + } + + xhtml = renderingContext.ConvertInternalLinks(xhtml); + + if (GlobalSettingsFacade.PrettifyPublicMarkup) + { + xhtml = renderingContext.FormatXhtml(xhtml); + } + + var response = context.Response; + + // Inserting perfomance profiling information + if (renderingContext.ProfilingEnabled) + { + xhtml = renderingContext.BuildProfilerReport(); + + response.ContentType = "text/xml"; + } + + response.Write(xhtml); + } + } + + void InitializeFullPageCaching(HttpContext context) + { + using (var page = new CachableEmptyPage()) + { + page.ProcessRequest(context); + } + } + + public bool IsReusable => true; + + private class CachableEmptyPage : Page + { + protected override void FrameworkInitialize() + { + base.FrameworkInitialize(); + + // That's an equivalent of having <%@ OutputCache CacheProfile="C1Page" %> + // on an *.aspx page + + InitOutputCache(new OutputCacheParameters + { + CacheProfile = CacheProfileName + }); + } + } + } +} diff --git a/Composite/Plugins/PageTemplates/Razor/RazorPageRenderer.cs b/Composite/Plugins/PageTemplates/Razor/RazorPageRenderer.cs index 8e77fe2e9b..25476cacfa 100644 --- a/Composite/Plugins/PageTemplates/Razor/RazorPageRenderer.cs +++ b/Composite/Plugins/PageTemplates/Razor/RazorPageRenderer.cs @@ -14,7 +14,7 @@ namespace Composite.Plugins.PageTemplates.Razor { - internal class RazorPageRenderer : IPageRenderer + internal class RazorPageRenderer : IPageRenderer, ISlimPageRenderer { private readonly Hashtable _renderingInfo; private readonly Hashtable _loadingExceptions; From 4e9410419c6f708e33f8585f5298164954f5f64b Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 16 Jun 2017 17:32:38 +0200 Subject: [PATCH 02/38] A prototype for donut caching implementation for razor templates and razor functions. --- Composite/AspNet/Razor/RazorFunction.cs | 16 +- Composite/Composite.csproj | 1 + .../Core/Caching/FileRelatedDataCache.cs | 2 +- .../PageTemplates/TemplateDefinitionHelper.cs | 11 +- .../Core/Routing/Pages/CmsPageHttpHandler.cs | 108 +++++++- .../WebClient/Renderings/Page/PageRenderer.cs | 232 ++++++++++-------- .../PluginFacades/FunctionWrapper.cs | 18 +- Composite/Functions/IDynamicFunction.cs | 13 + .../FileBasedFunctionProvider.cs | 21 +- .../RazorBasedFunction.cs | 42 ++-- .../RazorFunctionProvider.cs | 13 +- .../UserControlFunctionProvider.cs | 2 +- 12 files changed, 327 insertions(+), 152 deletions(-) create mode 100644 Composite/Functions/IDynamicFunction.cs diff --git a/Composite/AspNet/Razor/RazorFunction.cs b/Composite/AspNet/Razor/RazorFunction.cs index 80afb7347c..17235edf46 100644 --- a/Composite/AspNet/Razor/RazorFunction.cs +++ b/Composite/AspNet/Razor/RazorFunction.cs @@ -10,7 +10,6 @@ namespace Composite.AspNet.Razor /// Base class for c1 functions based on razor /// public abstract class RazorFunction : CompositeC1WebPage, IParameterWidgetsProvider - { /// /// Gets the function description. Override this to make a custom description. @@ -21,10 +20,7 @@ public abstract class RazorFunction : CompositeC1WebPage, IParameterWidgetsProvi /// get { return "Will show recent Twitter activity for a given keyword."; } ///} /// - public virtual string FunctionDescription - { - get { return string.Empty; } - } + public virtual string FunctionDescription => string.Empty; /// /// Gets the return type. By default this is XhtmlDocument (html). Override this to set another return type, like string or XElement. @@ -35,10 +31,12 @@ public virtual string FunctionDescription /// get { return typeof(string); } ///} /// - public virtual Type FunctionReturnType - { - get { return typeof (XhtmlDocument); } - } + public virtual Type FunctionReturnType => typeof (XhtmlDocument); + + /// + /// Determines whether the function output can be cached. + /// + public virtual bool PreventFunctionOutputCaching => false; /// public virtual IDictionary GetParameterWidgets() diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 9008ad24a6..cfd1a06c26 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -256,6 +256,7 @@ + diff --git a/Composite/Core/Caching/FileRelatedDataCache.cs b/Composite/Core/Caching/FileRelatedDataCache.cs index 5ea00703dd..2e3bed177d 100644 --- a/Composite/Core/Caching/FileRelatedDataCache.cs +++ b/Composite/Core/Caching/FileRelatedDataCache.cs @@ -125,7 +125,7 @@ private bool Get(string key, DateTime lastModifiedUtc, out CachedData cachedData } catch (Exception ex) { - Log.LogWarning(LogTitle, "Failed to load cached data. Cache '{0}', file: '{1}'", cacheFileName); + Log.LogWarning(LogTitle, $"Failed to load cached data. Cache '{key}', file: '{cacheFileName}'"); Log.LogWarning(LogTitle, ex); cachedData = null; diff --git a/Composite/Core/PageTemplates/TemplateDefinitionHelper.cs b/Composite/Core/PageTemplates/TemplateDefinitionHelper.cs index 946b95e455..f763bd11f6 100644 --- a/Composite/Core/PageTemplates/TemplateDefinitionHelper.cs +++ b/Composite/Core/PageTemplates/TemplateDefinitionHelper.cs @@ -125,14 +125,19 @@ public static void BindPlaceholders(IPageTemplate template, if (functionContextContainer != null) { + bool allFunctionsExecuted = false; + using (Profiler.Measure($"Evaluating placeholder '{placeholderId}'")) { - PageRenderer.ExecuteEmbeddedFunctions(placeholderXhtml.Root, functionContextContainer); + allFunctionsExecuted = PageRenderer.ExecuteCachebleFuctions(placeholderXhtml.Root, functionContextContainer); } - using (Profiler.Measure("Normalizing XHTML document")) + if (allFunctionsExecuted) { - PageRenderer.NormalizeXhtmlDocument(placeholderXhtml); + using (Profiler.Measure("Normalizing XHTML document")) + { + PageRenderer.NormalizeXhtmlDocument(placeholderXhtml); + } } } diff --git a/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs b/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs index 3c38a29565..b28ec90ec5 100644 --- a/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs +++ b/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs @@ -1,4 +1,6 @@ using System; +using System.Reflection; +using System.Runtime.Caching; using System.Web; using System.Web.UI; using System.Xml.Linq; @@ -11,35 +13,79 @@ namespace Composite.Core.Routing.Pages { + /// + /// Renders page tempates without building a Web Form's control tree. + /// Contains a custom implementation of "donut caching". + /// internal class CmsPageHttpHandler: IHttpHandler { private const string CacheProfileName = "C1Page"; + private static readonly FieldInfo CacheabilityFieldInfo; + + static CmsPageHttpHandler() + { + CacheabilityFieldInfo = typeof(HttpCachePolicy).GetField("_cacheability", BindingFlags.Instance | BindingFlags.NonPublic); + } public void ProcessRequest(HttpContext context) { InitializeFullPageCaching(context); + var cacheKey = GetCacheKey(context); + using (var renderingContext = RenderingContext.InitializeFromHttpContext()) { - if (renderingContext.RunResponseHandlers()) + var functionContext = PageRenderer.GetPageRenderFunctionContextContainer(); + + XDocument document; + using (Profiler.Measure("Cache lookup")) { - return; + document = GetFromCache(cacheKey); } - var renderer = PageTemplateFacade.BuildPageRenderer(renderingContext.Page.TemplateId); + bool allFunctionsExecuted = false; + if (document == null) + { + if (renderingContext.RunResponseHandlers()) + { + return; + } - var slimRenderer = (ISlimPageRenderer) renderer; + var renderer = PageTemplateFacade.BuildPageRenderer(renderingContext.Page.TemplateId); - var functionContext = PageRenderer.GetPageRenderFunctionContextContainer(); + var slimRenderer = (ISlimPageRenderer) renderer; - XDocument document; + using (Profiler.Measure($"{nameof(ISlimPageRenderer)}.Render")) + { + document = slimRenderer.Render(renderingContext.PageContentToRender, functionContext); + } + + allFunctionsExecuted = PageRenderer.ExecuteCachebleFuctions(document.Root, functionContext); + + if (!allFunctionsExecuted && ServerSideCachingEnabled(context)) + { + context.Response.Cache.SetNoServerCaching(); + + AddToCache(cacheKey, document); + } + } + else + { + context.Response.Cache.SetNoServerCaching(); + } - using (Profiler.Measure($"{nameof(ISlimPageRenderer)}.Render")) + if (!allFunctionsExecuted) { - document = slimRenderer.Render(renderingContext.PageContentToRender, functionContext); + using (Profiler.Measure("Executing embedded functions")) + { + PageRenderer.ExecuteEmbeddedFunctions(document.Root, functionContext); + } } - PageRenderer.ProcessPageDocument(document, functionContext, renderingContext.Page); + using (Profiler.Measure("Resolving page fields")) + { + PageRenderer.ResolvePageFields(document, renderingContext.Page); + } string xhtml; if (document.Root.Name == RenderingElementNames.Html) @@ -81,6 +127,17 @@ public void ProcessRequest(HttpContext context) } } + private bool ServerSideCachingEnabled(HttpContext context) + { + var cacheability = GetPageCacheablity(context); + + // TODO: a proper check here + return cacheability > HttpCacheability.NoCache; + } + + private HttpCacheability GetPageCacheablity(HttpContext context) + => (HttpCacheability) CacheabilityFieldInfo.GetValue(context.Response.Cache); + void InitializeFullPageCaching(HttpContext context) { using (var page = new CachableEmptyPage()) @@ -89,6 +146,37 @@ void InitializeFullPageCaching(HttpContext context) } } + private XDocument GetFromCache(string cacheKey) + { + // TODO: set the response headers as well + + var result = MemoryCache.Default.Get(cacheKey) as XDocument; + if (result != null) + { + result = new XDocument(result); + } + + return result; + } + + private void AddToCache(string cacheKey, XDocument document) + { + // TODO: use the standard ASP.NET cache storage providers + // TODO: preserve the response headers as well + + var copy = new XDocument(document); + MemoryCache.Default.Add(cacheKey, copy, new CacheItemPolicy + { + SlidingExpiration = TimeSpan.FromSeconds(60) + }); + } + + private string GetCacheKey(HttpContext context) + { + // TODO: implement properly + return context.Request.Url.ToString(); + } + public bool IsReusable => true; private class CachableEmptyPage : Page @@ -107,4 +195,4 @@ protected override void FrameworkInitialize() } } } -} +} \ No newline at end of file diff --git a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs index 22df48a2f8..ae37fae37b 100644 --- a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs +++ b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs @@ -30,6 +30,7 @@ public static class PageRenderer private static readonly string LogTitle = typeof(PageRenderer).Name; private static readonly NameBasedAttributeComparer _nameBasedAttributeComparer = new NameBasedAttributeComparer(); + private static readonly XName XName_function = Namespaces.Function10 + "function"; /// public static FunctionContextContainer GetPageRenderFunctionContextContainer() @@ -236,27 +237,6 @@ public static IEnumerable GetCurrentPageAssociatedData() where T : IDa } - internal static void ProcessPageDocument( - XDocument document, - FunctionContextContainer contextContainer, - IPage page) - { - using (Profiler.Measure("Executing embedded functions")) - { - ExecuteEmbeddedFunctions(document.Root, contextContainer); - } - - using (Profiler.Measure("Resolving page fields")) - { - ResolvePageFields(document, page); - } - - using (Profiler.Measure("Normalizing ASP.NET forms")) - { - NormalizeAspNetForms(document); - } - } - internal static void ProcessXhtmlDocument(XhtmlDocument xhtmlDocument, IPage page) { using (Profiler.Measure("Normalizing XHTML document")) @@ -291,7 +271,20 @@ public static Control Render(XDocument document, FunctionContextContainer contex { using (TimerProfilerFacade.CreateTimerProfiler()) { - ProcessPageDocument(document, contextContainer, page); + using (Profiler.Measure("Executing embedded functions")) + { + ExecuteEmbeddedFunctions(document.Root, contextContainer); + } + + using (Profiler.Measure("Resolving page fields")) + { + ResolvePageFields(document, page); + } + + using (Profiler.Measure("Normalizing ASP.NET forms")) + { + NormalizeAspNetForms(document); + } if (document.Root.Name != RenderingElementNames.Html) { @@ -445,7 +438,6 @@ private static void NormalizeAspNetForms(XDocument document) aspNetFormElement.ReplaceWith(aspNetFormElement.Nodes()); } } - } @@ -472,111 +464,151 @@ public static void ResolvePageFields(XDocument document, IPage page) } elem.ReplaceWith(new XElement(Namespaces.Xhtml + "meta", - new XAttribute("name", "description"), - new XAttribute("content", page.Description))); + new XAttribute("name", "description"), + new XAttribute("content", page.Description))); } } - - - /// - public static void ExecuteEmbeddedFunctions(XElement element, FunctionContextContainer contextContainer) + /// + /// Executes functions that match the predicate recursively, + /// + /// + /// + /// + /// A predicate that defines whether a function should be executed based on its name. + /// True if all of the functions has matched the predicate + internal static bool ExecuteFunctionsRec( + XElement element, + FunctionContextContainer functionContext, + Predicate functionShouldBeExecuted = null) { - using (TimerProfilerFacade.CreateTimerProfiler()) + if (element.Name != XName_function) { - IEnumerable functionCallDefinitions = element.DescendantsAndSelf(Namespaces.Function10 + "function") - .Where(f => !f.Ancestors(Namespaces.Function10 + "function").Any()); - - var functionCalls = functionCallDefinitions.ToList(); - if (functionCalls.Count == 0) return; - - object[] functionExecutionResults = new object[functionCalls.Count]; - - for (int i = 0; i < functionCalls.Count; i++) + var children = element.Elements(); + if (element.Elements(XName_function).Any()) { - XElement functionCallDefinition = functionCalls[i]; - string functionName = null; + // Allows replacing the function elements without breaking the iterator + children = children.ToList(); + } - object functionResult; - try + bool allChildrenExecuted = true; + foreach (var childElement in children) + { + if (!ExecuteFunctionsRec(childElement, functionContext, functionShouldBeExecuted)) { - // Evaluating function calls in parameters - IEnumerable parameters = functionCallDefinition.Elements(); - - foreach (XElement parameterNode in parameters) - { - ExecuteEmbeddedFunctions(parameterNode, contextContainer); - } - - - // Executing a function call - BaseRuntimeTreeNode runtimeTreeNode = FunctionTreeBuilder.Build(functionCallDefinition); - - functionName = runtimeTreeNode.GetAllSubFunctionNames().FirstOrDefault(); - - object result = runtimeTreeNode.GetValue(contextContainer); + allChildrenExecuted = false; + } + } + return allChildrenExecuted; + } - if (result != null) - { - // Evaluating functions in a result of a function call - object embedableResult = contextContainer.MakeXEmbedable(result); + bool allRecFunctionsExecuted = true; - foreach (XElement xelement in GetXElements(embedableResult)) - { - ExecuteEmbeddedFunctions(xelement, contextContainer); - } + string functionName = (string) element.Attribute("name"); + object result; + try + { + // Evaluating function calls in parameters + IEnumerable parameters = element.Elements(); - functionResult = embedableResult; - } - else - { - functionResult = null; - } - } - catch (Exception ex) + bool allParametersEvaluated = true; + foreach (XElement parameterNode in parameters.ToList()) + { + if (!ExecuteFunctionsRec(parameterNode, functionContext, functionShouldBeExecuted)) { - using (Profiler.Measure("PageRenderer. Loggin an exception")) - { - XElement errorBoxHtml; + allParametersEvaluated = false; + } + } - if (!contextContainer.ProcessException(functionName, ex, LogTitle, out errorBoxHtml)) - { - throw; - } + if (!allParametersEvaluated) + { + return false; + } - functionResult = errorBoxHtml; - } - } + if (functionShouldBeExecuted != null && + !functionShouldBeExecuted(functionName)) + { + return false; + } - functionExecutionResults[i] = functionResult; - }; + // Executing a function call + BaseRuntimeTreeNode runtimeTreeNode = FunctionTreeBuilder.Build(element); + result = runtimeTreeNode.GetValue(functionContext); - // Applying changes - for (int i = 0; i < functionCalls.Count; i++) + if (result != null) { - XElement functionCall = functionCalls[i]; - object functionCallResult = functionExecutionResults[i]; - if (functionCallResult != null) + // Evaluating functions in a result of a function call + result = functionContext.MakeXEmbedable(result); + + foreach (XElement xelement in GetXElements(result).ToList()) { - if (functionCallResult is XAttribute && functionCall.Parent != null) + if (!ExecuteFunctionsRec(xelement, functionContext, functionShouldBeExecuted)) { - functionCall.Parent.Add(functionCallResult); - functionCall.Remove(); - } - else - { - functionCall.ReplaceWith(functionCallResult); + allRecFunctionsExecuted = false; } } - else + } + } + catch (Exception ex) + { + using (Profiler.Measure("PageRenderer. Logging an exception")) + { + XElement errorBoxHtml; + + if (!functionContext.ProcessException(functionName, ex, LogTitle, out errorBoxHtml)) { - functionCall.Remove(); + throw; } + + result = errorBoxHtml; } } + + ReplaceFunctionWithResult(element, result); + + return allRecFunctionsExecuted; + } + + /// + public static void ExecuteEmbeddedFunctions(XElement element, FunctionContextContainer functionContext) + { + ExecuteFunctionsRec(element, functionContext, null); } + /// + /// Executes all cacheble (not dynamic) functions and returs True + /// if all of the functions were cacheble. + /// + /// + /// + /// + internal static bool ExecuteCachebleFuctions(XElement element, FunctionContextContainer functionContext) + { + return ExecuteFunctionsRec(element, functionContext, name => + { + var function = FunctionFacade.GetFunction(name) as IDynamicFunction; + return function == null || !function.PreventFunctionOutputCaching; + }); + } + private static void ReplaceFunctionWithResult(XElement functionCall, object result) + { + if (result == null) + { + functionCall.Remove(); + return; + } + + if (result is XAttribute && functionCall.Parent != null) + { + functionCall.Parent.Add(result); + functionCall.Remove(); + } + else + { + functionCall.ReplaceWith(result); + } + } private static IEnumerable GetXElements(object source) { diff --git a/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs b/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs index 6e31908801..9c9b3999d8 100644 --- a/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs +++ b/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Web; @@ -14,7 +14,7 @@ namespace Composite.Functions.Foundation.PluginFacades /// This class is used for catching exceptions from plugins and handling them correctly /// [DebuggerDisplay("Name = {Name}, Namespace = {Namespace}")] - internal sealed class FunctionWrapper : IDowncastableFunction, ICompoundFunction, IFunctionInitializationInfo + internal sealed class FunctionWrapper : IDowncastableFunction, ICompoundFunction, IFunctionInitializationInfo, IDynamicFunction { private static readonly string LogTitle = typeof (FunctionWrapper).Name; private readonly IFunction _functionToWrap; @@ -139,8 +139,8 @@ public bool AllowRecursiveCall { get { - return _functionToWrap is ICompoundFunction - && (_functionToWrap as ICompoundFunction).AllowRecursiveCall; + return _functionToWrap is ICompoundFunction compoundFunction + && compoundFunction.AllowRecursiveCall; } } @@ -156,5 +156,15 @@ bool IFunctionInitializationInfo.FunctionInitializedCorrectly return ((IFunctionInitializationInfo) _functionToWrap).FunctionInitializedCorrectly; } } + + public bool PreventFunctionOutputCaching + { + get + { + var dynamicFunction = _functionToWrap as IDynamicFunction; + return dynamicFunction != null && dynamicFunction.PreventFunctionOutputCaching; + } + } + } } diff --git a/Composite/Functions/IDynamicFunction.cs b/Composite/Functions/IDynamicFunction.cs new file mode 100644 index 0000000000..d2a01d746a --- /dev/null +++ b/Composite/Functions/IDynamicFunction.cs @@ -0,0 +1,13 @@ +namespace Composite.Functions +{ + /// + /// Allows defining whether function results should be cached during the donut caching. + /// + public interface IDynamicFunction : IFunction + { + /// + /// Indicates whether the function output can be cached. + /// + bool PreventFunctionOutputCaching { get; } + } +} \ No newline at end of file diff --git a/Composite/Plugins/Functions/FunctionProviders/FileBasedFunctionProvider/FileBasedFunctionProvider.cs b/Composite/Plugins/Functions/FunctionProviders/FileBasedFunctionProvider/FileBasedFunctionProvider.cs index e8adb0b19e..ab10600679 100644 --- a/Composite/Plugins/Functions/FunctionProviders/FileBasedFunctionProvider/FileBasedFunctionProvider.cs +++ b/Composite/Plugins/Functions/FunctionProviders/FileBasedFunctionProvider/FileBasedFunctionProvider.cs @@ -109,7 +109,8 @@ public IEnumerable Functions function = InstantiateFunctionFromCache(virtualPath, @namespace, name, cachedFunctionInfo.ReturnType, - cachedFunctionInfo.Description); + cachedFunctionInfo.Description, + cachedFunctionInfo.PreventCaching); } } catch (ThreadAbortException) @@ -213,13 +214,15 @@ protected FileBasedFunctionProvider(string name, string folder) /// The name. /// Cached value of return type. /// Cached value of the description. + /// Cached PreventFunctionOutputCache property value. /// protected virtual IFunction InstantiateFunctionFromCache( string virtualPath, string @namespace, string name, Type returnType, - string cachedDescription) + string cachedDescription, + bool preventCaching) { return InstantiateFunction(virtualPath, @namespace, name); } @@ -280,6 +283,7 @@ private class CachedFunctionInformation { public Type ReturnType { get; private set; } public string Description { get; private set; } + public bool PreventCaching { get; private set; } private CachedFunctionInformation() {} @@ -287,6 +291,8 @@ public CachedFunctionInformation(IFunction function) { ReturnType = function.ReturnType; Description = function.Description; + PreventCaching = function is IDynamicFunction dynamicFunction + && dynamicFunction.PreventFunctionOutputCaching; } public static void Serialize(CachedFunctionInformation data, string filePath) @@ -296,6 +302,7 @@ public static void Serialize(CachedFunctionInformation data, string filePath) if (data != null) { lines.Add(TypeManager.SerializeType(data.ReturnType)); + lines.Add(data.PreventCaching.ToString()); lines.AddRange(data.Description.Split(new [] { Environment.NewLine }, StringSplitOptions.None)); } @@ -313,9 +320,15 @@ public static CachedFunctionInformation Deserialize(string filePath) Type type = TypeManager.TryGetType(lines[0]); if (type == null) return null; - string description = string.Join(Environment.NewLine, lines.Skip(1)); + bool preventCaching = bool.Parse(lines[1]); + string description = string.Join(Environment.NewLine, lines.Skip(2)); - return new CachedFunctionInformation { Description = description, ReturnType = type }; + return new CachedFunctionInformation + { + Description = description, + PreventCaching = preventCaching, + ReturnType = type + }; } } } diff --git a/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorBasedFunction.cs b/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorBasedFunction.cs index 8501dacbdb..d57a0d56df 100644 --- a/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorBasedFunction.cs +++ b/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorBasedFunction.cs @@ -13,16 +13,21 @@ namespace Composite.Plugins.Functions.FunctionProviders.RazorFunctionProvider { [DebuggerDisplay("Razor function: {Namespace + '.' + Name}")] - internal class RazorBasedFunction : FileBasedFunction + internal class RazorBasedFunction : FileBasedFunction, IDynamicFunction { - public RazorBasedFunction(string ns, string name, string description, IDictionary parameters, Type returnType, string virtualPath, FileBasedFunctionProvider provider) + public RazorBasedFunction(string ns, string name, string description, + IDictionary parameters, Type returnType, string virtualPath, + bool preventCaching, + FileBasedFunctionProvider provider) : base(ns, name, description, parameters, returnType, virtualPath, provider) { - } + PreventFunctionOutputCaching = preventCaching; + } - public RazorBasedFunction(string ns, string name, string description, Type returnType, string virtualPath, FileBasedFunctionProvider provider) + public RazorBasedFunction(string ns, string name, string description, Type returnType, string virtualPath, bool preventCaching, FileBasedFunctionProvider provider) : base(ns, name, description, returnType, virtualPath, provider) { + PreventFunctionOutputCaching = preventCaching; } protected override void InitializeParameters() @@ -36,12 +41,14 @@ protected override void InitializeParameters() razorPage = WebPage.CreateInstanceFromVirtualPath(VirtualPath); } - if (!(razorPage is RazorFunction)) + var razorFunction = razorPage as RazorFunction; + if (razorFunction == null) { throw new InvalidOperationException($"Failed to initialize function from cache. Path: '{VirtualPath}'"); } - Parameters = FunctionBasedFunctionProviderHelper.GetParameters(razorPage as RazorFunction, typeof (RazorFunction), VirtualPath); + Parameters = FunctionBasedFunctionProviderHelper.GetParameters(razorFunction, typeof (RazorFunction), VirtualPath); + PreventFunctionOutputCaching = razorFunction.PreventFunctionOutputCaching; } finally { @@ -51,21 +58,21 @@ protected override void InitializeParameters() public override object Execute(ParameterList parameters, FunctionContextContainer context) { - Action setParametersAction = webPageBase => + void SetParametersAction(WebPageBase webPageBase) { - foreach (var param in parameters.AllParameterNames) - { - var parameter = Parameters[param]; + foreach (var param in parameters.AllParameterNames) + { + var parameter = Parameters[param]; - object parameterValue = parameters.GetParameter(param); + object parameterValue = parameters.GetParameter(param); - parameter.SetValue(webPageBase, parameterValue); - } - }; + parameter.SetValue(webPageBase, parameterValue); + } + } - try + try { - return RazorHelper.ExecuteRazorPage(VirtualPath, setParametersAction, ReturnType, context); + return RazorHelper.ExecuteRazorPage(VirtualPath, SetParametersAction, ReturnType, context); } catch (Exception ex) { @@ -102,5 +109,8 @@ private void EmbedExecutionExceptionSourceCode(Exception ex) } } } + + /// + public bool PreventFunctionOutputCaching { get; protected set; } } } diff --git a/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorFunctionProvider.cs b/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorFunctionProvider.cs index 81e6e9fcda..3f2ab541e0 100644 --- a/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorFunctionProvider.cs +++ b/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorFunctionProvider.cs @@ -40,8 +40,13 @@ protected override IFunction InstantiateFunction(string virtualPath, string @nam var functionParameters = FunctionBasedFunctionProviderHelper.GetParameters( razorFunction, typeof(RazorFunction), virtualPath); - return new RazorBasedFunction(@namespace, name, razorFunction.FunctionDescription, functionParameters, - razorFunction.FunctionReturnType, virtualPath, this); + return new RazorBasedFunction(@namespace, name, + razorFunction.FunctionDescription, + functionParameters, + razorFunction.FunctionReturnType, + virtualPath, + razorFunction.PreventFunctionOutputCaching, + this); } finally { @@ -49,11 +54,11 @@ protected override IFunction InstantiateFunction(string virtualPath, string @nam } } - protected override IFunction InstantiateFunctionFromCache(string virtualPath, string @namespace, string name, Type returnType, string cachedDescription) + protected override IFunction InstantiateFunctionFromCache(string virtualPath, string @namespace, string name, Type returnType, string cachedDescription, bool preventCaching) { if (returnType != null) { - return new RazorBasedFunction(@namespace, name, cachedDescription, returnType, virtualPath, this); + return new RazorBasedFunction(@namespace, name, cachedDescription, returnType, virtualPath, preventCaching, this); } return InstantiateFunction(virtualPath, @namespace, name); diff --git a/Composite/Plugins/Functions/FunctionProviders/UserControlFunctionProvider/UserControlFunctionProvider.cs b/Composite/Plugins/Functions/FunctionProviders/UserControlFunctionProvider/UserControlFunctionProvider.cs index a900f60fc5..3746513bba 100644 --- a/Composite/Plugins/Functions/FunctionProviders/UserControlFunctionProvider/UserControlFunctionProvider.cs +++ b/Composite/Plugins/Functions/FunctionProviders/UserControlFunctionProvider/UserControlFunctionProvider.cs @@ -58,7 +58,7 @@ protected override IFunction InstantiateFunction(string virtualPath, string @nam return new UserControlBasedFunction(@namespace, name, description, parameters, typeof(UserControl), virtualPath, this); } - protected override IFunction InstantiateFunctionFromCache(string virtualPath, string @namespace, string name, Type returnType, string cachedDescription) + protected override IFunction InstantiateFunctionFromCache(string virtualPath, string @namespace, string name, Type returnType, string cachedDescription, bool preventCaching) { return new UserControlBasedFunction(@namespace, name, cachedDescription, virtualPath, this); } From bae5a49c1f8a0a51538f49a8141e2788a3b2c019 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 19 Jun 2017 14:11:14 +0200 Subject: [PATCH 03/38] Fixing thread culture not being set for CmsPageHttpHandler --- Composite/Core/WebClient/Renderings/RenderingContext.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Composite/Core/WebClient/Renderings/RenderingContext.cs b/Composite/Core/WebClient/Renderings/RenderingContext.cs index 81b5360349..f39e2f3579 100644 --- a/Composite/Core/WebClient/Renderings/RenderingContext.cs +++ b/Composite/Core/WebClient/Renderings/RenderingContext.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; using System.Web; using Composite.C1Console.Security; using Composite.Core.Extensions; @@ -240,7 +241,11 @@ private void InitializeFromHttpContextInternal() PageRenderer.CurrentPage = Page; - _dataScope = new DataScope(Page.DataSourceId.PublicationScope, Page.DataSourceId.LocaleScope); + var culture = Page.DataSourceId.LocaleScope; + + _dataScope = new DataScope(Page.DataSourceId.PublicationScope, culture); + Thread.CurrentThread.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; var pagePlaceholderContents = GetPagePlaceholderContents(); PageContentToRender = new PageContentToRender(Page, pagePlaceholderContents, PreviewMode); From 46b05309178122ca1fd6c821db857309a88e3316 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 20 Jun 2017 10:21:58 +0200 Subject: [PATCH 04/38] Implementing donut caching --- .../Core/Routing/Pages/CmsPageHttpHandler.cs | 113 ++++++++++++++---- 1 file changed, 92 insertions(+), 21 deletions(-) diff --git a/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs b/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs index b28ec90ec5..52ff947c08 100644 --- a/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs +++ b/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Reflection; using System.Runtime.Caching; using System.Web; +using System.Web.Caching; using System.Web.UI; using System.Xml.Linq; using Composite.Core.Configuration; @@ -13,6 +15,39 @@ namespace Composite.Core.Routing.Pages { + [Serializable] + internal class DonutCacheEntry + { + private XDocument _document; + + public DonutCacheEntry() + { + } + + public DonutCacheEntry(HttpContext context, XDocument document) + { + Document = new XDocument(document); + + var headers = context.Response.Headers; + + var headersCopy = new List(headers.Count); + foreach (var name in headers.AllKeys) + { + headersCopy.Add(new HeaderElement(name, headers[name])); + } + + OutputHeaders = headersCopy; + } + + public XDocument Document + { + get => new XDocument(_document); + set => _document = value; + } + + public IReadOnlyCollection OutputHeaders { get; set; } + } + /// /// Renders page tempates without building a Web Form's control tree. /// Contains a custom implementation of "donut caching". @@ -38,13 +73,27 @@ public void ProcessRequest(HttpContext context) var functionContext = PageRenderer.GetPageRenderFunctionContextContainer(); XDocument document; + DonutCacheEntry cacheEntry; using (Profiler.Measure("Cache lookup")) { - document = GetFromCache(cacheKey); + cacheEntry = GetFromCache(context, cacheKey); } bool allFunctionsExecuted = false; - if (document == null) + bool preventResponseCaching = false; + + if (cacheEntry != null) + { + document = cacheEntry.Document; + foreach (var header in cacheEntry.OutputHeaders) + { + context.Response.Headers[header.Name] = header.Value; + } + + // Making sure this response will not go to the output cache + preventResponseCaching = true; + } + else { if (renderingContext.RunResponseHandlers()) { @@ -64,15 +113,14 @@ public void ProcessRequest(HttpContext context) if (!allFunctionsExecuted && ServerSideCachingEnabled(context)) { - context.Response.Cache.SetNoServerCaching(); + preventResponseCaching = true; - AddToCache(cacheKey, document); + using (Profiler.Measure("Adding to cache")) + { + AddToCache(context, cacheKey, new DonutCacheEntry(context, document)); + } } } - else - { - context.Response.Cache.SetNoServerCaching(); - } if (!allFunctionsExecuted) { @@ -115,6 +163,11 @@ public void ProcessRequest(HttpContext context) var response = context.Response; + if (preventResponseCaching) + { + context.Response.Cache.SetNoServerCaching(); + } + // Inserting perfomance profiling information if (renderingContext.ProfilingEnabled) { @@ -129,10 +182,18 @@ public void ProcessRequest(HttpContext context) private bool ServerSideCachingEnabled(HttpContext context) { + if (context.Response.StatusCode != 200) + { + return false; + } + +#if !DEBUG var cacheability = GetPageCacheablity(context); // TODO: a proper check here return cacheability > HttpCacheability.NoCache; +#endif + return true; } private HttpCacheability GetPageCacheablity(HttpContext context) @@ -146,29 +207,39 @@ void InitializeFullPageCaching(HttpContext context) } } - private XDocument GetFromCache(string cacheKey) + private DonutCacheEntry GetFromCache(HttpContext context, string cacheKey) { - // TODO: set the response headers as well + var provider = GetCacheProvider(context); - var result = MemoryCache.Default.Get(cacheKey) as XDocument; - if (result != null) + if (provider == null) { - result = new XDocument(result); + return MemoryCache.Default.Get(cacheKey) as DonutCacheEntry; } - return result; + return provider.Get(cacheKey) as DonutCacheEntry; } - private void AddToCache(string cacheKey, XDocument document) + private void AddToCache(HttpContext context, string cacheKey, DonutCacheEntry entry) { - // TODO: use the standard ASP.NET cache storage providers - // TODO: preserve the response headers as well + var provider = GetCacheProvider(context); - var copy = new XDocument(document); - MemoryCache.Default.Add(cacheKey, copy, new CacheItemPolicy + if (provider == null) { - SlidingExpiration = TimeSpan.FromSeconds(60) - }); + MemoryCache.Default.Add(cacheKey, entry, new CacheItemPolicy + { + SlidingExpiration = TimeSpan.FromSeconds(60) + }); + return; + } + + provider.Add(cacheKey, entry, DateTime.UtcNow.AddSeconds(60)); + } + + OutputCacheProvider GetCacheProvider(HttpContext context) + { + var cacheName = context.ApplicationInstance.GetOutputCacheProviderName(context); + + return cacheName != "AspNetInternalProvider" ? OutputCache.Providers?[cacheName] : null; } private string GetCacheKey(HttpContext context) From 7b44238cabe6466b881d2fcd5c135adfb7ec0d30 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 22 Jun 2017 13:40:06 +0200 Subject: [PATCH 05/38] Donut caching - support for varyByParam/varyByHeader caching profile settings; refactoring --- Composite/AspNet/Caching/DonutCacheEntry.cs | 41 +++ Composite/AspNet/Caching/OutputCacheHelper.cs | 202 +++++++++++++ Composite/AspNet/CmsPageHttpHandler.cs | 143 ++++++++++ Composite/Composite.csproj | 4 +- .../Core/Routing/Pages/C1PageRouteHander.cs | 3 +- .../Core/Routing/Pages/CmsPageHttpHandler.cs | 269 ------------------ 6 files changed, 391 insertions(+), 271 deletions(-) create mode 100644 Composite/AspNet/Caching/DonutCacheEntry.cs create mode 100644 Composite/AspNet/Caching/OutputCacheHelper.cs create mode 100644 Composite/AspNet/CmsPageHttpHandler.cs delete mode 100644 Composite/Core/Routing/Pages/CmsPageHttpHandler.cs diff --git a/Composite/AspNet/Caching/DonutCacheEntry.cs b/Composite/AspNet/Caching/DonutCacheEntry.cs new file mode 100644 index 0000000000..b981259d2d --- /dev/null +++ b/Composite/AspNet/Caching/DonutCacheEntry.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.Caching; +using System.Xml.Linq; + +namespace Composite.AspNet.Caching +{ + [Serializable] + internal class DonutCacheEntry + { + private XDocument _document; + + public DonutCacheEntry() + { + } + + public DonutCacheEntry(HttpContext context, XDocument document) + { + Document = new XDocument(document); + + var headers = context.Response.Headers; + + var headersCopy = new List(headers.Count); + foreach (var name in headers.AllKeys) + { + headersCopy.Add(new HeaderElement(name, headers[name])); + } + + OutputHeaders = headersCopy; + } + + public XDocument Document + { + get => new XDocument(_document); + set => _document = value; + } + + public IReadOnlyCollection OutputHeaders { get; set; } + } +} diff --git a/Composite/AspNet/Caching/OutputCacheHelper.cs b/Composite/AspNet/Caching/OutputCacheHelper.cs new file mode 100644 index 0000000000..2b69f408c4 --- /dev/null +++ b/Composite/AspNet/Caching/OutputCacheHelper.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Reflection; +using System.Runtime.Caching; +using System.Text; +using System.Web; +using System.Web.Caching; +using System.Web.Configuration; +using System.Web.Hosting; +using System.Web.UI; + +namespace Composite.AspNet.Caching +{ + internal static class OutputCacheHelper + { + private const string CacheProfileName = "C1Page"; + private static readonly FieldInfo CacheabilityFieldInfo; + + private static readonly Dictionary _outputCacheProfiles; + + static OutputCacheHelper() + { + CacheabilityFieldInfo = typeof(HttpCachePolicy).GetField("_cacheability", BindingFlags.Instance | BindingFlags.NonPublic); + + _outputCacheProfiles = new Dictionary(); + + var settings = WebConfigurationManager.OpenWebConfiguration(HostingEnvironment.ApplicationVirtualPath) + .GetSection("system.web/caching/outputCacheSettings") as OutputCacheSettingsSection; + + if (settings != null) + { + foreach (OutputCacheProfile profile in settings.OutputCacheProfiles) + { + _outputCacheProfiles[profile.Name] = profile; + } + } + } + + /// + /// Returns true and sets the cache key value for the current request if + /// ASP.NET full page caching is enabled. + /// + /// + /// + /// + public static bool TryGetCacheKey(HttpContext context, out string cacheKey) + { + var cacheProfile = _outputCacheProfiles[CacheProfileName]; + + if (!cacheProfile.Enabled || cacheProfile.Duration <= 0 + || !(cacheProfile.Location == (OutputCacheLocation) (-1) /* Unspecified */ + || cacheProfile.Location == OutputCacheLocation.Any + || cacheProfile.Location == OutputCacheLocation.Server + || cacheProfile.Location == OutputCacheLocation.ServerAndClient)) + { + cacheKey = null; + return false; + } + + var request = context.Request; + + var sb = new StringBuilder(1 + request.Path.Length + (request.PathInfo ?? "").Length ); + + sb.Append(request.HttpMethod[0]).Append(request.Path).Append(request.PathInfo); + + if (cacheProfile.VaryByCustom != null) + { + string custom = context.ApplicationInstance.GetVaryByCustomString(context, cacheProfile.VaryByCustom); + sb.Append("c").Append(custom); + } + + if (!string.IsNullOrEmpty(cacheProfile.VaryByParam)) + { + var filter = GetVaryByFilter(cacheProfile.VaryByParam); + + AppendParameters(sb, "Q", request.QueryString, filter); + + if (request.HttpMethod == "POST") + { + AppendParameters(sb, "F", request.Form, filter); + } + } + + if (!string.IsNullOrEmpty(cacheProfile.VaryByHeader)) + { + var filter = GetVaryByFilter(cacheProfile.VaryByHeader); + + AppendParameters(sb, "H", request.Headers, filter); + } + + cacheKey = sb.ToString(); + return true; + } + + private static Func GetVaryByFilter(string varyBy) + { + if (varyBy == "*") + { + return parameter => true; + } + + var list = varyBy.Split(';'); + return parameter => list.Contains(parameter); + } + + + private static void AppendParameters(StringBuilder sb, string cacheKeyDelimiter, NameValueCollection collection, Func filter) + { + foreach (string key in collection.OfType().Where(filter)) + { + sb.Append(cacheKeyDelimiter).Append(key).Append("=").Append(collection[key]); + } + } + + + public static DonutCacheEntry GetFromCache(HttpContext context, string cacheKey) + { + var provider = GetCacheProvider(context); + + if (provider == null) + { + return MemoryCache.Default.Get(cacheKey) as DonutCacheEntry; + } + + return provider.Get(cacheKey) as DonutCacheEntry; + } + + + public static void AddToCache(HttpContext context, string cacheKey, DonutCacheEntry entry) + { + var provider = GetCacheProvider(context); + + if (provider == null) + { + MemoryCache.Default.Add(cacheKey, entry, new CacheItemPolicy + { + SlidingExpiration = TimeSpan.FromSeconds(60) + }); + return; + } + + provider.Add(cacheKey, entry, DateTime.UtcNow.AddSeconds(60)); + } + + + static OutputCacheProvider GetCacheProvider(HttpContext context) + { + var cacheName = context.ApplicationInstance.GetOutputCacheProviderName(context); + + return cacheName != "AspNetInternalProvider" ? OutputCache.Providers?[cacheName] : null; + } + + + public static bool ResponseCachebale(HttpContext context) + { + if (context.Response.StatusCode != 200) + { + return false; + } + +#if !DEBUG + var cacheability = GetPageCacheablity(context); + + return cacheability > HttpCacheability.NoCache; +#endif + return true; + } + + + private static HttpCacheability GetPageCacheablity(HttpContext context) + => (HttpCacheability)CacheabilityFieldInfo.GetValue(context.Response.Cache); + + + + public static void InitializeFullPageCaching(HttpContext context) + { + using (var page = new CachableEmptyPage()) + { + page.ProcessRequest(context); + } + } + + + private class CachableEmptyPage : Page + { + protected override void FrameworkInitialize() + { + base.FrameworkInitialize(); + + // That's an equivalent of having <%@ OutputCache CacheProfile="C1Page" %> + // on an *.aspx page + + InitOutputCache(new OutputCacheParameters + { + CacheProfile = CacheProfileName + }); + } + } + } +} diff --git a/Composite/AspNet/CmsPageHttpHandler.cs b/Composite/AspNet/CmsPageHttpHandler.cs new file mode 100644 index 0000000000..79caeb53fa --- /dev/null +++ b/Composite/AspNet/CmsPageHttpHandler.cs @@ -0,0 +1,143 @@ +using System.Web; +using System.Xml.Linq; +using Composite.AspNet.Caching; +using Composite.Core.Configuration; +using Composite.Core.Instrumentation; +using Composite.Core.PageTemplates; +using Composite.Core.WebClient.Renderings; +using Composite.Core.WebClient.Renderings.Page; +using Composite.Core.Xml; + +namespace Composite.AspNet +{ + /// + /// Renders page tempates without building a Web Form's control tree. + /// Contains a custom implementation of "donut caching". + /// + internal class CmsPageHttpHandler: IHttpHandler + { + public void ProcessRequest(HttpContext context) + { + OutputCacheHelper.InitializeFullPageCaching(context); + + bool cachingEnabled = OutputCacheHelper.TryGetCacheKey(context, out string cacheKey); + + using (var renderingContext = RenderingContext.InitializeFromHttpContext()) + { + var functionContext = PageRenderer.GetPageRenderFunctionContextContainer(); + + XDocument document; + DonutCacheEntry cacheEntry = null; + if (cachingEnabled) + { + using (Profiler.Measure("Cache lookup")) + { + cacheEntry = OutputCacheHelper.GetFromCache(context, cacheKey); + } + } + + bool allFunctionsExecuted = false; + bool preventResponseCaching = false; + + if (cacheEntry != null) + { + document = cacheEntry.Document; + foreach (var header in cacheEntry.OutputHeaders) + { + context.Response.Headers[header.Name] = header.Value; + } + + // Making sure this response will not go to the output cache + preventResponseCaching = true; + } + else + { + if (renderingContext.RunResponseHandlers()) + { + return; + } + + var renderer = PageTemplateFacade.BuildPageRenderer(renderingContext.Page.TemplateId); + + var slimRenderer = (ISlimPageRenderer) renderer; + + using (Profiler.Measure($"{nameof(ISlimPageRenderer)}.Render")) + { + document = slimRenderer.Render(renderingContext.PageContentToRender, functionContext); + } + + allFunctionsExecuted = PageRenderer.ExecuteCachebleFuctions(document.Root, functionContext); + + if (cachingEnabled && !allFunctionsExecuted && OutputCacheHelper.ResponseCachebale(context)) + { + preventResponseCaching = true; + + using (Profiler.Measure("Adding to cache")) + { + OutputCacheHelper.AddToCache(context, cacheKey, new DonutCacheEntry(context, document)); + } + } + } + + if (!allFunctionsExecuted) + { + using (Profiler.Measure("Executing embedded functions")) + { + PageRenderer.ExecuteEmbeddedFunctions(document.Root, functionContext); + } + } + + using (Profiler.Measure("Resolving page fields")) + { + PageRenderer.ResolvePageFields(document, renderingContext.Page); + } + + string xhtml; + if (document.Root.Name == RenderingElementNames.Html) + { + var xhtmlDocument = new XhtmlDocument(document); + + PageRenderer.ProcessXhtmlDocument(xhtmlDocument, renderingContext.Page); + + xhtml = xhtmlDocument.ToString(); + } + else + { + xhtml = document.ToString(); + } + + if (renderingContext.PreRenderRedirectCheck()) + { + return; + } + + xhtml = renderingContext.ConvertInternalLinks(xhtml); + + if (GlobalSettingsFacade.PrettifyPublicMarkup) + { + xhtml = renderingContext.FormatXhtml(xhtml); + } + + var response = context.Response; + + if (preventResponseCaching) + { + context.Response.Cache.SetNoServerCaching(); + } + + // Inserting perfomance profiling information + if (renderingContext.ProfilingEnabled) + { + xhtml = renderingContext.BuildProfilerReport(); + + response.ContentType = "text/xml"; + } + + response.Write(xhtml); + } + } + + + public bool IsReusable => true; + } +} \ No newline at end of file diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index cfd1a06c26..6a35e8695d 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -251,8 +251,10 @@ + + - + diff --git a/Composite/Core/Routing/Pages/C1PageRouteHander.cs b/Composite/Core/Routing/Pages/C1PageRouteHander.cs index 5c842df1cb..2cf13d13af 100644 --- a/Composite/Core/Routing/Pages/C1PageRouteHander.cs +++ b/Composite/Core/Routing/Pages/C1PageRouteHander.cs @@ -9,6 +9,7 @@ using System.Web.Routing; using System.Web.UI; using System.Xml.Linq; +using Composite.AspNet; using Composite.Core.Extensions; using Composite.Core.Linq; using Composite.Core.PageTemplates; @@ -54,7 +55,7 @@ static C1PageRouteHandler() _handlerType = Type.GetType(typeAttr.Value); if(_handlerType == null) { - Log.LogError(typeof(C1PageRouteHandler).Name, $"Failed to load type '{typeAttr.Value}'"); + Log.LogError(nameof(C1PageRouteHandler), $"Failed to load type '{typeAttr.Value}'"); } } } diff --git a/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs b/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs deleted file mode 100644 index 52ff947c08..0000000000 --- a/Composite/Core/Routing/Pages/CmsPageHttpHandler.cs +++ /dev/null @@ -1,269 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.Caching; -using System.Web; -using System.Web.Caching; -using System.Web.UI; -using System.Xml.Linq; -using Composite.Core.Configuration; -using Composite.Core.Instrumentation; -using Composite.Core.PageTemplates; -using Composite.Core.WebClient.Renderings; -using Composite.Core.WebClient.Renderings.Page; -using Composite.Core.Xml; - -namespace Composite.Core.Routing.Pages -{ - [Serializable] - internal class DonutCacheEntry - { - private XDocument _document; - - public DonutCacheEntry() - { - } - - public DonutCacheEntry(HttpContext context, XDocument document) - { - Document = new XDocument(document); - - var headers = context.Response.Headers; - - var headersCopy = new List(headers.Count); - foreach (var name in headers.AllKeys) - { - headersCopy.Add(new HeaderElement(name, headers[name])); - } - - OutputHeaders = headersCopy; - } - - public XDocument Document - { - get => new XDocument(_document); - set => _document = value; - } - - public IReadOnlyCollection OutputHeaders { get; set; } - } - - /// - /// Renders page tempates without building a Web Form's control tree. - /// Contains a custom implementation of "donut caching". - /// - internal class CmsPageHttpHandler: IHttpHandler - { - private const string CacheProfileName = "C1Page"; - private static readonly FieldInfo CacheabilityFieldInfo; - - static CmsPageHttpHandler() - { - CacheabilityFieldInfo = typeof(HttpCachePolicy).GetField("_cacheability", BindingFlags.Instance | BindingFlags.NonPublic); - } - - public void ProcessRequest(HttpContext context) - { - InitializeFullPageCaching(context); - - var cacheKey = GetCacheKey(context); - - using (var renderingContext = RenderingContext.InitializeFromHttpContext()) - { - var functionContext = PageRenderer.GetPageRenderFunctionContextContainer(); - - XDocument document; - DonutCacheEntry cacheEntry; - using (Profiler.Measure("Cache lookup")) - { - cacheEntry = GetFromCache(context, cacheKey); - } - - bool allFunctionsExecuted = false; - bool preventResponseCaching = false; - - if (cacheEntry != null) - { - document = cacheEntry.Document; - foreach (var header in cacheEntry.OutputHeaders) - { - context.Response.Headers[header.Name] = header.Value; - } - - // Making sure this response will not go to the output cache - preventResponseCaching = true; - } - else - { - if (renderingContext.RunResponseHandlers()) - { - return; - } - - var renderer = PageTemplateFacade.BuildPageRenderer(renderingContext.Page.TemplateId); - - var slimRenderer = (ISlimPageRenderer) renderer; - - using (Profiler.Measure($"{nameof(ISlimPageRenderer)}.Render")) - { - document = slimRenderer.Render(renderingContext.PageContentToRender, functionContext); - } - - allFunctionsExecuted = PageRenderer.ExecuteCachebleFuctions(document.Root, functionContext); - - if (!allFunctionsExecuted && ServerSideCachingEnabled(context)) - { - preventResponseCaching = true; - - using (Profiler.Measure("Adding to cache")) - { - AddToCache(context, cacheKey, new DonutCacheEntry(context, document)); - } - } - } - - if (!allFunctionsExecuted) - { - using (Profiler.Measure("Executing embedded functions")) - { - PageRenderer.ExecuteEmbeddedFunctions(document.Root, functionContext); - } - } - - using (Profiler.Measure("Resolving page fields")) - { - PageRenderer.ResolvePageFields(document, renderingContext.Page); - } - - string xhtml; - if (document.Root.Name == RenderingElementNames.Html) - { - var xhtmlDocument = new XhtmlDocument(document); - - PageRenderer.ProcessXhtmlDocument(xhtmlDocument, renderingContext.Page); - - xhtml = xhtmlDocument.ToString(); - } - else - { - xhtml = document.ToString(); - } - - if (renderingContext.PreRenderRedirectCheck()) - { - return; - } - - xhtml = renderingContext.ConvertInternalLinks(xhtml); - - if (GlobalSettingsFacade.PrettifyPublicMarkup) - { - xhtml = renderingContext.FormatXhtml(xhtml); - } - - var response = context.Response; - - if (preventResponseCaching) - { - context.Response.Cache.SetNoServerCaching(); - } - - // Inserting perfomance profiling information - if (renderingContext.ProfilingEnabled) - { - xhtml = renderingContext.BuildProfilerReport(); - - response.ContentType = "text/xml"; - } - - response.Write(xhtml); - } - } - - private bool ServerSideCachingEnabled(HttpContext context) - { - if (context.Response.StatusCode != 200) - { - return false; - } - -#if !DEBUG - var cacheability = GetPageCacheablity(context); - - // TODO: a proper check here - return cacheability > HttpCacheability.NoCache; -#endif - return true; - } - - private HttpCacheability GetPageCacheablity(HttpContext context) - => (HttpCacheability) CacheabilityFieldInfo.GetValue(context.Response.Cache); - - void InitializeFullPageCaching(HttpContext context) - { - using (var page = new CachableEmptyPage()) - { - page.ProcessRequest(context); - } - } - - private DonutCacheEntry GetFromCache(HttpContext context, string cacheKey) - { - var provider = GetCacheProvider(context); - - if (provider == null) - { - return MemoryCache.Default.Get(cacheKey) as DonutCacheEntry; - } - - return provider.Get(cacheKey) as DonutCacheEntry; - } - - private void AddToCache(HttpContext context, string cacheKey, DonutCacheEntry entry) - { - var provider = GetCacheProvider(context); - - if (provider == null) - { - MemoryCache.Default.Add(cacheKey, entry, new CacheItemPolicy - { - SlidingExpiration = TimeSpan.FromSeconds(60) - }); - return; - } - - provider.Add(cacheKey, entry, DateTime.UtcNow.AddSeconds(60)); - } - - OutputCacheProvider GetCacheProvider(HttpContext context) - { - var cacheName = context.ApplicationInstance.GetOutputCacheProviderName(context); - - return cacheName != "AspNetInternalProvider" ? OutputCache.Providers?[cacheName] : null; - } - - private string GetCacheKey(HttpContext context) - { - // TODO: implement properly - return context.Request.Url.ToString(); - } - - public bool IsReusable => true; - - private class CachableEmptyPage : Page - { - protected override void FrameworkInitialize() - { - base.FrameworkInitialize(); - - // That's an equivalent of having <%@ OutputCache CacheProfile="C1Page" %> - // on an *.aspx page - - InitOutputCache(new OutputCacheParameters - { - CacheProfile = CacheProfileName - }); - } - } - } -} \ No newline at end of file From 4cd010cfb5a54c54a3c3efa9a37b1d1c27a0af40 Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Thu, 22 Jun 2017 14:51:48 +0200 Subject: [PATCH 06/38] cleanup.bat encoding being utf8, where byte order mark os confusing cmd.exe in first line. now a comment. --- Website/App_Data/Composite/reset-installation.bat | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Website/App_Data/Composite/reset-installation.bat b/Website/App_Data/Composite/reset-installation.bat index 855974e8d0..160fe60fd5 100644 --- a/Website/App_Data/Composite/reset-installation.bat +++ b/Website/App_Data/Composite/reset-installation.bat @@ -1,5 +1,7 @@ -del DataMetaData\*.xml -del DataStores\*.xml +:: clean up +del DataMetaData\*.* /q +del DataStores\*.* /q +pause del Configuration\DynamicSqlDataProvider.config del Configuration\DynamicXmlDataProvider.config del Configuration\InstallationInformation.xml From 5edf2cff9fe712eefd8d97fc7148aeef1a4bebe9 Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Tue, 11 Jul 2017 11:17:52 +0200 Subject: [PATCH 07/38] cleanup.bat fix :/ --- Website/App_Data/Composite/reset-installation.bat | 1 - 1 file changed, 1 deletion(-) diff --git a/Website/App_Data/Composite/reset-installation.bat b/Website/App_Data/Composite/reset-installation.bat index 160fe60fd5..6a6bf10f89 100644 --- a/Website/App_Data/Composite/reset-installation.bat +++ b/Website/App_Data/Composite/reset-installation.bat @@ -1,7 +1,6 @@ :: clean up del DataMetaData\*.* /q del DataStores\*.* /q -pause del Configuration\DynamicSqlDataProvider.config del Configuration\DynamicXmlDataProvider.config del Configuration\InstallationInformation.xml From f2518412e2726d48c166257fd1731b6af88adb4b Mon Sep 17 00:00:00 2001 From: mawtex Date: Thu, 28 Nov 2019 15:35:00 +0100 Subject: [PATCH 08/38] Type fix --- .../String/TreeSelectorWidgetFunction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/String/TreeSelectorWidgetFunction.cs b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/String/TreeSelectorWidgetFunction.cs index 3438ea107d..4f8448f449 100644 --- a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/String/TreeSelectorWidgetFunction.cs +++ b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/String/TreeSelectorWidgetFunction.cs @@ -68,7 +68,7 @@ private void SetParameterProfiles() new ConstantValueProvider(string.Empty), StandardWidgetFunctions.TextBoxWidget, null, - "Search Token", new HelpDefinition("A search token, seriallized, to filter which tree elements is shown. To filter what is selectable, use the 'Selection filter' properties."))); + "Search Token", new HelpDefinition("A search token, serialized, to filter which tree elements is shown. To filter what is selectable, use the 'Selection filter' properties."))); base.AddParameterProfile( new ParameterProfile("Required", typeof(bool), From 0475c1bd11e664466e5f2ee5fdff02fffe7d0ae7 Mon Sep 17 00:00:00 2001 From: Inna Boitsun Date: Tue, 17 Dec 2019 16:47:55 +0200 Subject: [PATCH 09/38] Filter Search Results by PageTypeId (#714) Filter Search Results by PageTypeId --- .../Core/ResourceSystem/LocalizationFiles.cs | 146 +++++++++++++++++- Composite/Data/Types/IPage.cs | 1 + .../Crawling/DefaultDataFieldProcessor.cs | 3 +- Composite/Search/SearchQuery.cs | 19 ++- .../localization/Composite.Search.en-us.xml | 5 +- 5 files changed, 162 insertions(+), 12 deletions(-) diff --git a/Composite/Core/ResourceSystem/LocalizationFiles.cs b/Composite/Core/ResourceSystem/LocalizationFiles.cs index e5be4a0b8a..43dd67c5e7 100644 --- a/Composite/Core/ResourceSystem/LocalizationFiles.cs +++ b/Composite/Core/ResourceSystem/LocalizationFiles.cs @@ -2906,7 +2906,7 @@ public static class Composite_Plugins_GeneratedDataTypesElementProvider { public static string Add=>T("Add"); ///"Add new global datatype" public static string AddToolTip=>T("AddToolTip"); -///"List Unpublished Data" +///"List Unpublished Content" public static string ViewUnpublishedItems=>T("ViewUnpublishedItems"); ///"Get an overview of data that haven't been published yet" public static string ViewUnpublishedItemsToolTip=>T("ViewUnpublishedItemsToolTip"); @@ -3940,13 +3940,13 @@ public static class Composite_Plugins_PageElementProvider { public static string PageElementProvider_AddPageAtRootFormat(object parameter0)=>string.Format(T("PageElementProvider.AddPageAtRootFormat"), parameter0); ///"Add new homepage" public static string PageElementProvider_AddPageAtRootToolTip=>T("PageElementProvider.AddPageAtRootToolTip"); -///"List Unpublished Pages" +///"List Unpublished Content" public static string PageElementProvider_ViewUnpublishedItems=>T("PageElementProvider.ViewUnpublishedItems"); -///"Get an overview of pages and page folder data that haven't been published yet." +///"Get an overview of pages and other content that haven't been published yet." public static string PageElementProvider_ViewUnpublishedItemsToolTip=>T("PageElementProvider.ViewUnpublishedItemsToolTip"); -///"Unpublished content" +///"Unpublished Content" public static string PageElementProvider_ViewUnpublishedItems_document_title=>T("PageElementProvider.ViewUnpublishedItems-document-title"); -///"The list below displays pages and page data which are currently being edited or are ready to be approved / published." +///"The list below displays pages and other content which are currently in draft or are ready to be approved / published." public static string PageElementProvider_ViewUnpublishedItems_document_description=>T("PageElementProvider.ViewUnpublishedItems-document-description"); ///"Edit Page" public static string PageElementProvider_EditPage=>T("PageElementProvider.EditPage"); @@ -4042,7 +4042,7 @@ public static class Composite_Plugins_PageElementProvider { public static string DeletePageWorkflow_ConfirmAllVersionsDeletion_DeleteAllVersions=>T("DeletePageWorkflow.ConfirmAllVersionsDeletion.DeleteAllVersions"); ///"Delete only current version" public static string DeletePageWorkflow_ConfirmAllVersionsDeletion_DeleteCurrentVersion=>T("DeletePageWorkflow.ConfirmAllVersionsDeletion.DeleteCurrentVersion"); -///"Page Title" +///"Title" public static string ViewUnpublishedItems_PageTitleLabel=>T("ViewUnpublishedItems.PageTitleLabel"); ///"Version" public static string ViewUnpublishedItems_VersionLabel=>T("ViewUnpublishedItems.VersionLabel"); @@ -6224,14 +6224,20 @@ public static class Composite_Plugins_UserGroupElementProvider { public static string EditUserGroup_EditUserGroupStep1_ActivePerspectiveFieldLabel=>T("EditUserGroup.EditUserGroupStep1.ActivePerspectiveFieldLabel"); ///"Perspectives" public static string EditUserGroup_EditUserGroupStep1_ActivePerspectiveMultiSelectLabel=>T("EditUserGroup.EditUserGroupStep1.ActivePerspectiveMultiSelectLabel"); -///"Select which perspectives the user gets access to view" +///"Select which perspectives the users of this group gets access to view" public static string EditUserGroup_EditUserGroupStep1_ActivePerspectiveMultiSelectHelp=>T("EditUserGroup.EditUserGroupStep1.ActivePerspectiveMultiSelectHelp"); ///"Global permissions" public static string EditUserGroup_EditUserGroupStep1_GlobalPermissionsFieldLabel=>T("EditUserGroup.EditUserGroupStep1.GlobalPermissionsFieldLabel"); ///"Global permissions" public static string EditUserGroup_EditUserGroupStep1_GlobalPermissionsMultiSelectLabel=>T("EditUserGroup.EditUserGroupStep1.GlobalPermissionsMultiSelectLabel"); -///"The Administrate permission grants the user group access to manage user group permissions and execute other administrative tasks. The Configure permission grants access to super user tasks." +///"Global permissions that users in this group should have. The Administrate permission grants the user group access to manage user group permissions and execute other administrative tasks. The Configure permission grants access to super user tasks." public static string EditUserGroup_EditUserGroupStep1_GlobalPermissionsMultiSelectHelp=>T("EditUserGroup.EditUserGroupStep1.GlobalPermissionsMultiSelectHelp"); +///"Data Language Access" +public static string EditUserGroup_EditUserGroupStep1_ActiveLocalesFieldLabel=>T("EditUserGroup.EditUserGroupStep1.ActiveLocalesFieldLabel"); +///"Data Languages" +public static string EditUserGroup_EditUserGroupStep1_ActiveLocalesMultiSelectLabel=>T("EditUserGroup.EditUserGroupStep1.ActiveLocalesMultiSelectLabel"); +///"Users in this group has access to manage data in the selected languages." +public static string EditUserGroup_EditUserGroupStep1_ActiveLocalesMultiSelectHelp=>T("EditUserGroup.EditUserGroupStep1.ActiveLocalesMultiSelectHelp"); ///"User Group Has Users" public static string DeleteUserGroup_DeleteUserGroupInitialStep_UserGroupHasUsersTitle=>T("DeleteUserGroup.DeleteUserGroupInitialStep.UserGroupHasUsersTitle"); ///"You cannot delete a user group that has users." @@ -6666,6 +6672,8 @@ public static class Composite_Search { public static string DataType_Page=>T("DataType.Page"); ///"Media File" public static string DataType_MediaFile=>T("DataType.MediaFile"); +///"Page Type" +public static string FieldNames_PageTypeId=>T("FieldNames.PageTypeId"); ///"Label" public static string FieldNames_Label=>T("FieldNames.Label"); ///"Description" @@ -6697,6 +6705,8 @@ public static class Untranslated { public const string DataType_Page="${Composite.Search,DataType.Page}"; ///"Media File" public const string DataType_MediaFile="${Composite.Search,DataType.MediaFile}"; +///"Page Type" +public const string FieldNames_PageTypeId="${Composite.Search,FieldNames.PageTypeId}"; ///"Label" public const string FieldNames_Label="${Composite.Search,FieldNames.Label}"; ///"Description" @@ -7213,6 +7223,10 @@ public static class Composite_Web_SourceEditor { public static string Toolbar_Format_Label=>T("Toolbar.Format.Label"); ///"Format XML source" public static string Toolbar_Format_ToolTip=>T("Toolbar.Format.ToolTip"); +///"Toggle word wrap" +public static string Toolbar_ToggleWordWrap_Label=>T("Toolbar.ToggleWordWrap.Label"); +///"Find and replace" +public static string Toolbar_FindAndReplace_Label=>T("Toolbar.FindAndReplace.Label"); ///"Page URL" public static string Insert_PageURL_Label=>T("Insert.PageURL.Label"); ///"Image URL" @@ -7239,6 +7253,24 @@ public static class Composite_Web_SourceEditor { public static string ResxEditor_TranslatedText=>T("ResxEditor.TranslatedText"); ///"Save" public static string ResxEditor_Save=>T("ResxEditor.Save"); +///"Find and replace" +public static string FindAndReplace_LabelTitle=>T("FindAndReplace.LabelTitle"); +///"Find" +public static string FindAndReplace_LabelFind=>T("FindAndReplace.LabelFind"); +///"Replace with" +public static string FindAndReplace_LabelReplaceWith=>T("FindAndReplace.LabelReplaceWith"); +///"Match case" +public static string FindAndReplace_LabelMatchCase=>T("FindAndReplace.LabelMatchCase"); +///"Whole words" +public static string FindAndReplace_LabelWholeWords=>T("FindAndReplace.LabelWholeWords"); +///"Find Next" +public static string FindAndReplace_ButtonFind=>T("FindAndReplace.ButtonFind"); +///"Replace" +public static string FindAndReplace_ButtonReplace=>T("FindAndReplace.ButtonReplace"); +///"Replace all" +public static string FindAndReplace_ButtonReplaceAll=>T("FindAndReplace.ButtonReplaceAll"); +///"Find and Replace" +public static string FindAndReplace_LaunchButton_Label=>T("FindAndReplace.LaunchButton.Label"); private static string T(string key) => StringResourceSystemFacade.GetString("Composite.Web.SourceEditor", key); } @@ -7569,6 +7601,28 @@ public static class Composite_Web_VisualEditor { public static string SpellCheck_InfoCaption=>T("SpellCheck.InfoCaption"); ///"To get suggestions for a misspelled word, press your SHIFT key down when you invoke the context menu." public static string SpellCheck_InfoText=>T("SpellCheck.InfoText"); +///"Find and Replace" +public static string SearchAndReplace_LaunchButton_Label=>T("SearchAndReplace.LaunchButton.Label"); +///"Find and replace" +public static string SearchAndReplace_LabelTitle=>T("SearchAndReplace.LabelTitle"); +///"Find" +public static string SearchAndReplace_LabelFind=>T("SearchAndReplace.LabelFind"); +///"Replace with" +public static string SearchAndReplace_LabelReplaceWith=>T("SearchAndReplace.LabelReplaceWith"); +///"Match case" +public static string SearchAndReplace_LabelMatchCase=>T("SearchAndReplace.LabelMatchCase"); +///"Whole words" +public static string SearchAndReplace_LabelWholeWords=>T("SearchAndReplace.LabelWholeWords"); +///"Find Next" +public static string SearchAndReplace_ButtonFind=>T("SearchAndReplace.ButtonFind"); +///"Replace" +public static string SearchAndReplace_ButtonReplace=>T("SearchAndReplace.ButtonReplace"); +///"Replace all" +public static string SearchAndReplace_ButtonReplaceAll=>T("SearchAndReplace.ButtonReplaceAll"); +///"nothing was found" +public static string SearchAndReplace_NothingFoundMessage=>T("SearchAndReplace.NothingFoundMessage"); +///"item(s) found" +public static string SearchAndReplace_ItemsWereFoundMessage=>T("SearchAndReplace.ItemsWereFoundMessage"); ///"Edit" public static string Function_Edit=>T("Function.Edit"); ///"Edit {0}" @@ -7590,6 +7644,82 @@ public static class Composite_Web_VisualEditor { ///"Cancel" public static string Components_Window_Cancel=>T("Components.Window.Cancel"); private static string T(string key) => StringResourceSystemFacade.GetString("Composite.Web.VisualEditor", key); +} + + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public static class Orckestra_Tools_UrlConfiguration { +///"URL Configuration" +public static string Tree_ConfigurationElementLabel=>T("Tree.ConfigurationElementLabel"); +///"This section allows configuring shorter and friendlier urls" +public static string Tree_ConfigurationElementToolTip=>T("Tree.ConfigurationElementToolTip"); +///"Edit URL Configuration" +public static string Tree_ConfigurationElementEditLabel=>T("Tree.ConfigurationElementEditLabel"); +///"Edit URL Configuration" +public static string Tree_ConfigurationElementEditToolTip=>T("Tree.ConfigurationElementEditToolTip"); +///"Hostnames" +public static string Tree_HostnamesFolderLabel=>T("Tree.HostnamesFolderLabel"); +///"Here you can map a hostname to a site" +public static string Tree_HostnamesFolderToolTip=>T("Tree.HostnamesFolderToolTip"); +///"Add Hostname" +public static string Tree_AddHostnameLabel=>T("Tree.AddHostnameLabel"); +///"Add a new hostname mapping" +public static string Tree_AddHostnameToolTip=>T("Tree.AddHostnameToolTip"); +///"Edit Hostname" +public static string Tree_EditHostnameLabel=>T("Tree.EditHostnameLabel"); +///"Edit this hostname mapping" +public static string Tree_EditHostnameToolTip=>T("Tree.EditHostnameToolTip"); +///"Delete Hostname" +public static string Tree_DeleteHostnameLabel=>T("Tree.DeleteHostnameLabel"); +///"Delete this hostname mapping" +public static string Tree_DeleteHostnameToolTip=>T("Tree.DeleteHostnameToolTip"); +///"UrlConfiguration" +public static string Tree_UrlConfigurationLabel=>T("Tree.UrlConfigurationLabel"); +///"URL Configuration" +public static string UrlConfiguration_Title=>T("UrlConfiguration.Title"); +///"Page URL Suffix" +public static string UrlConfiguration_PageUrlSuffix_Label=>T("UrlConfiguration.PageUrlSuffix.Label"); +///"A string that will be appended to all page urls. F.e. '.aspx' or '.html', leaving this field empty will produce extensionless urls" +public static string UrlConfiguration_PageUrlSuffix_Help=>T("UrlConfiguration.PageUrlSuffix.Help"); +///"New Hostname" +public static string HostnameBinding_AddNewHostnameTitle=>T("HostnameBinding.AddNewHostnameTitle"); +///"Hostname" +public static string HostnameBinding_Hostname_Label=>T("HostnameBinding.Hostname.Label"); +///"Hostname to which current url building rules will be applied" +public static string HostnameBinding_Hostname_Help=>T("HostnameBinding.Hostname.Help"); +///"Page" +public static string HostnameBinding_Page_Label=>T("HostnameBinding.Page.Label"); +///"Root page that will be the default page for the current hostname" +public static string HostnameBinding_Page_Help=>T("HostnameBinding.Page.Help"); +///"URL" +public static string HostnameBinding_IncludeHomepageUrlTitle_Label=>T("HostnameBinding.IncludeHomepageUrlTitle.Label"); +///"Include homepage URL Title" +public static string HostnameBinding_IncludeHomepageUrlTitle_ItemLabel=>T("HostnameBinding.IncludeHomepageUrlTitle.ItemLabel"); +///"Determines whether root page's title should be a part of url. Not having it checked produces shorter urls" +public static string HostnameBinding_IncludeHomepageUrlTitle_Help=>T("HostnameBinding.IncludeHomepageUrlTitle.Help"); +///"Include language URL mapping" +public static string HostnameBinding_IncludeLanguageUrlMapping_ItemLabel=>T("HostnameBinding.IncludeLanguageUrlMapping.ItemLabel"); +///"Determines whether language code should be a part of a url" +public static string HostnameBinding_IncludeLanguageUrlMapping_Help=>T("HostnameBinding.IncludeLanguageUrlMapping.Help"); +///"Enforce HTTPS" +public static string HostnameBinding_EnforceHttps_ItemLabel=>T("HostnameBinding.EnforceHttps.ItemLabel"); +///"When checked, all the HTTP requests will be redirected to HTTPS links" +public static string HostnameBinding_EnforceHttps_Help=>T("HostnameBinding.EnforceHttps.Help"); +///"Custom 404 Page" +public static string HostnameBinding_Custom404Page_Label=>T("HostnameBinding.Custom404Page.Label"); +///"Url to which request will be redirected in the case there's a request to non-existent c1 page" +public static string HostnameBinding_Custom404Page_Help=>T("HostnameBinding.Custom404Page.Help"); +///"Alias hostnames" +public static string HostnameBinding_Aliases_Label=>T("HostnameBinding.Aliases.Label"); +///"Hostnames from which all requests will be redirected to the current hostname" +public static string HostnameBinding_Aliases_Help=>T("HostnameBinding.Aliases.Help"); +///"Alias Redirect" +public static string HostnameBinding_UsePermanentRedirect_Label=>T("HostnameBinding.UsePermanentRedirect.Label"); +///"Use permanent redirect (HTTP 301)" +public static string HostnameBinding_UsePermanentRedirect_ItemLabel=>T("HostnameBinding.UsePermanentRedirect.ItemLabel"); +///"When redirecting from an alias to the common hostname, a permanent redirect will tell visitors (browsers and search engines) that this redirect should be considered permanent and may be cached. Checking this box has a positive effect on SEO, provided the alias rule do not change in the near future" +public static string HostnameBinding_UsePermanentRedirect_Help=>T("HostnameBinding.UsePermanentRedirect.Help"); +private static string T(string key) => StringResourceSystemFacade.GetString("Orckestra.Tools.UrlConfiguration", key); } } diff --git a/Composite/Data/Types/IPage.cs b/Composite/Data/Types/IPage.cs index 214ec69d38..90d3d3654a 100644 --- a/Composite/Data/Types/IPage.cs +++ b/Composite/Data/Types/IPage.cs @@ -47,6 +47,7 @@ public interface IPage : IData, IChangeHistory, ICreationHistory, IPublishContro [ImmutableFieldId("{69303D16-F681-4C2F-BA73-AF8B2B94AAB2}")] [ForeignKey(typeof(IPageType), "Id", NullReferenceValue = "{00000000-0000-0000-0000-000000000000}", NullReferenceValueType = typeof(Guid))] [DefaultFieldGuidValue("{00000000-0000-0000-0000-000000000000}")] + [SearchableField(false, true, true)] Guid PageTypeId { get; set; } diff --git a/Composite/Search/Crawling/DefaultDataFieldProcessor.cs b/Composite/Search/Crawling/DefaultDataFieldProcessor.cs index df96d08cea..81164cd7c4 100644 --- a/Composite/Search/Crawling/DefaultDataFieldProcessor.cs +++ b/Composite/Search/Crawling/DefaultDataFieldProcessor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -133,6 +133,7 @@ public virtual string GetFieldLabel(PropertyInfo propertyInfo) if (fieldName == DocumentFieldNames.LastUpdated) return Texts.FieldNames_LastUpdated; if (propertyInfo.Name == nameof(IChangeHistory.ChangedBy)) return Texts.FieldNames_UpdatedBy; if (propertyInfo.Name == nameof(IMediaFile.MimeType)) return Texts.FieldNames_MimeType; + if (propertyInfo.Name == nameof(IPage.PageTypeId)) return Texts.FieldNames_PageTypeId; var frpAttribute = propertyInfo.GetCustomAttribute(); if (!string.IsNullOrEmpty(frpAttribute?.Label)) diff --git a/Composite/Search/SearchQuery.cs b/Composite/Search/SearchQuery.cs index 2201dc209e..7f31ce2fa7 100644 --- a/Composite/Search/SearchQuery.cs +++ b/Composite/Search/SearchQuery.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -6,6 +6,7 @@ using Composite.C1Console.Security; using Composite.Core.Threading; using Composite.Data; +using Composite.Data.Types; namespace Composite.Search { @@ -147,6 +148,22 @@ public void FilterByDataTypes(params Type[] dataTypes) }); } + /// + /// Filters search results by page types. + /// + /// + public void FilterByPageTypes(params string[] pageTypes) + { + Verify.ArgumentNotNull(pageTypes, nameof(pageTypes)); + + Selection.Add(new SearchQuerySelection + { + FieldName = DocumentFieldNames.GetFieldName(typeof(IPage), nameof(IPage.PageTypeId)), + Operation = SearchQuerySelectionOperation.Or, + Values = pageTypes + }); + } + /// /// Only documents that have the property set should be returned. /// diff --git a/Website/Composite/localization/Composite.Search.en-us.xml b/Website/Composite/localization/Composite.Search.en-us.xml index d1c210bec2..bfa8ecf536 100644 --- a/Website/Composite/localization/Composite.Search.en-us.xml +++ b/Website/Composite/localization/Composite.Search.en-us.xml @@ -1,4 +1,4 @@ - + @@ -9,7 +9,8 @@ - + + From f5976f6a69e8a7ca99d64641e800cc4b2f34a6a6 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 18 Dec 2019 11:34:36 +0100 Subject: [PATCH 10/38] Refactoring --- .../String/TreeSelectorWidgetFunction.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/String/TreeSelectorWidgetFunction.cs b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/String/TreeSelectorWidgetFunction.cs index 4f8448f449..1ff52b866b 100644 --- a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/String/TreeSelectorWidgetFunction.cs +++ b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/String/TreeSelectorWidgetFunction.cs @@ -1,20 +1,14 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; using System.Xml.Linq; - using Composite.Functions; using Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.Foundation; using Composite.C1Console.Forms.CoreUiControls; -using Composite.Core.Xml; namespace Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.String { internal sealed class TreeSelectorWidgetFunction : CompositeWidgetFunctionBase { - private const string _functionName = "TreeSelector"; - public const string CompositeName = CompositeWidgetFunctionBase.CommonNamespace + ".String." + _functionName; + private const string FunctionName = "TreeSelector"; + public const string CompositeName = CompositeWidgetFunctionBase.CommonNamespace + ".String." + FunctionName; public TreeSelectorWidgetFunction(EntityTokenFactory entityTokenFactory) : base(CompositeName, typeof(string), entityTokenFactory) @@ -26,7 +20,7 @@ public TreeSelectorWidgetFunction(EntityTokenFactory entityTokenFactory) private void SetParameterProfiles() { base.AddParameterProfile( - new ParameterProfile("ElementProvider", + new ParameterProfile(nameof(TreeSelectorUiControl.ElementProvider), typeof(string), true, new ConstantValueProvider(string.Empty), @@ -35,7 +29,7 @@ private void SetParameterProfiles() "Element Provider", new HelpDefinition("The name of a tree element provider (as defined in Composite.config)"))); base.AddParameterProfile( - new ParameterProfile("SelectableElementReturnValue", + new ParameterProfile(nameof(TreeSelectorUiControl.SelectableElementReturnValue), typeof(string), true, new ConstantValueProvider("EntityToken"), @@ -44,7 +38,7 @@ private void SetParameterProfiles() "Element field to return", new HelpDefinition("The name of the element field whose value to return for selection. Typical values here can be DataId (for data trees), Uri (for linkable elements), or EntityToken (for any element). Element providers may provide more fields."))); base.AddParameterProfile( - new ParameterProfile("SelectableElementPropertyName", + new ParameterProfile(nameof(TreeSelectorUiControl.SelectableElementPropertyName), typeof(string), false, new ConstantValueProvider(string.Empty), @@ -53,7 +47,7 @@ private void SetParameterProfiles() "Selection filter, Property Name", new HelpDefinition("An element must have this field to be selectable."))); base.AddParameterProfile( - new ParameterProfile("SelectableElementPropertyValue", + new ParameterProfile(nameof(TreeSelectorUiControl.SelectableElementPropertyValue), typeof(string), false, new ConstantValueProvider(string.Empty), @@ -62,7 +56,7 @@ private void SetParameterProfiles() "Selection filter, Property Value", new HelpDefinition("The value of the property optionally used (if provided) to further identify a selectable tree element by. Seperate multiple values with spaces."))); base.AddParameterProfile( - new ParameterProfile("SerializedSearchToken", + new ParameterProfile(nameof(TreeSelectorUiControl.SerializedSearchToken), typeof(string), false, new ConstantValueProvider(string.Empty), @@ -70,7 +64,7 @@ private void SetParameterProfiles() null, "Search Token", new HelpDefinition("A search token, serialized, to filter which tree elements is shown. To filter what is selectable, use the 'Selection filter' properties."))); base.AddParameterProfile( - new ParameterProfile("Required", + new ParameterProfile(nameof(TreeSelectorUiControl.Required), typeof(bool), false, new ConstantValueProvider(true), @@ -83,10 +77,15 @@ private void SetParameterProfiles() public override XElement GetWidgetMarkup(ParameterList parameters, string label, HelpDefinition helpDefinition, string bindingSourceName) { - XElement formElement = base.BuildBasicWidgetMarkup("TreeSelector", "SelectedKey", label, helpDefinition, bindingSourceName); - foreach (var propertyName in new [] + XElement formElement = base.BuildBasicWidgetMarkup("TreeSelector", nameof(TreeSelectorUiControl.SelectedKey), label, helpDefinition, bindingSourceName); + foreach (var propertyName in new [] { - "ElementProvider", "SelectableElementReturnValue", "SelectableElementPropertyName", "SelectableElementPropertyValue", "SerializedSearchToken", "Required" + nameof(TreeSelectorUiControl.ElementProvider), + nameof(TreeSelectorUiControl.SelectableElementReturnValue), + nameof(TreeSelectorUiControl.SelectableElementPropertyName), + nameof(TreeSelectorUiControl.SelectableElementPropertyValue), + nameof(TreeSelectorUiControl.SerializedSearchToken), + nameof(TreeSelectorUiControl.Required) }) { string propertyValue = parameters.GetParameter(propertyName); From 1aee73d8709c05a17e2363fd997625c39d858c09 Mon Sep 17 00:00:00 2001 From: igor-dovgaliuk <55481744+igor-dovgaliuk@users.noreply.github.com> Date: Tue, 24 Dec 2019 12:54:14 +0200 Subject: [PATCH 11/38] Feature/27591 tree page widget (#715) added ability to limit tree widget by home page AB#27591 --- .../C1Console/Actions/ActionExecutorFacade.cs | 14 +- .../Workflow/Activities/FormsWorkflow.cs | 7 + Composite/Composite.csproj | 3 + .../FormFlowUiDefinitionRenderer.cs | 4 +- .../FlowMediators/TreeServicesFacade.cs | 940 +++++++++--------- .../Renderings/Page/PageStructureInfo.cs | 15 +- Composite/Data/Types/PageServices.cs | 11 +- .../PageElementProvider.cs | 44 +- .../PageElementProvider/PageSearchToken.cs | 15 + .../Pages/GetPageIdFunction.cs | 82 +- .../HomePageSelectorWidgetFunction.cs | 60 ++ ...ablePageReferenceSelectorWidgetFunction.cs | 35 +- .../PageReferenceSelectorWidgetFunction.cs | 33 +- ...PageReferenceSelectorWidgetFunctionBase.cs | 90 ++ 14 files changed, 789 insertions(+), 564 deletions(-) create mode 100644 Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageSearchToken.cs create mode 100644 Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/HomePageSelectorWidgetFunction.cs create mode 100644 Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunctionBase.cs diff --git a/Composite/C1Console/Actions/ActionExecutorFacade.cs b/Composite/C1Console/Actions/ActionExecutorFacade.cs index 81af54eb0a..92acc93ce7 100644 --- a/Composite/C1Console/Actions/ActionExecutorFacade.cs +++ b/Composite/C1Console/Actions/ActionExecutorFacade.cs @@ -1,7 +1,8 @@ -//#warning REMARK THIS!!! +//#warning REMARK THIS!!! //#define NO_SECURITY using System; using System.Collections.Generic; +using System.Web; using Composite.C1Console.Actions.Foundation; using Composite.C1Console.Actions.Workflows; using Composite.C1Console.Events; @@ -33,6 +34,7 @@ public static FlowToken Execute(EntityToken entityToken, ActionToken actionToken if (entityToken == null) throw new ArgumentNullException("entityToken"); if (actionToken == null) throw new ArgumentNullException("actionToken"); + AddEntityTokenToContext(entityToken); string username = UserValidationFacade.GetUsername(); #if NO_SECURITY @@ -117,7 +119,15 @@ public static FlowToken Execute(EntityToken entityToken, ActionToken actionToken return flowToken; } - + internal const string HttpContextItem_EntityToken = "EntityToken"; + private static void AddEntityTokenToContext(EntityToken entityToken) + { + var httpContext = HttpContext.Current; + if (httpContext == null) + return; + + httpContext.Items[HttpContextItem_EntityToken] = entityToken; + } /// public static FlowToken ExecuteEntityTokenLocked(ActionToken lockedActionToken, EntityToken lockedEntityToken, FlowControllerServicesContainer flowControllerServicesContainer) diff --git a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs index 03e079504e..427ddd8395 100644 --- a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs +++ b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs @@ -96,8 +96,15 @@ protected override void Initialize(IServiceProvider provider) } base.Initialize(provider); + + if (!BindingExist(EntityTokenKey)) + { + Bindings.Add(EntityTokenKey, EntityToken); + } } + internal static readonly string EntityTokenKey = typeof(FormsWorkflow).FullName + ":EntityToken"; + /// protected override void Uninitialize(IServiceProvider provider) diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 936fe0a436..3cbbab2188 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -259,11 +259,14 @@ + ASPXCodeBehind + + diff --git a/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs b/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs index 4af3c8d67d..e34b06eae1 100644 --- a/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs +++ b/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -273,7 +273,7 @@ private static IUiContainer GetRenderingContainer(IFormChannelIdentifier channel - private static FormTreeCompiler CurrentFormTreeCompiler + internal static FormTreeCompiler CurrentFormTreeCompiler { get { return HttpContext.Current.Items[_formTreeCompilerLookupKey] as FormTreeCompiler; } set { HttpContext.Current.Items[_formTreeCompilerLookupKey] = value; } diff --git a/Composite/Core/WebClient/FlowMediators/TreeServicesFacade.cs b/Composite/Core/WebClient/FlowMediators/TreeServicesFacade.cs index 13c8b76057..62b558d465 100644 --- a/Composite/Core/WebClient/FlowMediators/TreeServicesFacade.cs +++ b/Composite/Core/WebClient/FlowMediators/TreeServicesFacade.cs @@ -1,470 +1,470 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Composite.C1Console.Events; -using Composite.C1Console.Elements; -using Composite.Core.Logging; -using Composite.Core.ResourceSystem.Icons; -using Composite.C1Console.Security; -using Composite.C1Console.Users; -using Composite.Core.WebClient.Services.TreeServiceObjects; -using Composite.Core.WebClient.Services.TreeServiceObjects.ExtensionMethods; - - -namespace Composite.Core.WebClient.FlowMediators -{ - internal class NullRootEntityToken : EntityToken - { - public override string Type { get { return "null"; } } - public override string Source { get { return "null"; } } - public override string Id { get { return "null"; } } - public override string Serialize() { return "NullRootEntiryToken"; } - } - - /// - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public static class TreeServicesFacade - { - /// - public static ClientElement GetRoot() - { - List roots = ElementFacade.GetRoots(null).ToList(); - - if (roots.Count == 0) - { - // user with out any access logging in - return "empty root" - roots = ElementFacade.GetRootsWithNoSecurity().ToList(); - if (roots.Count == 0) throw new InvalidOperationException("No roots specified"); - if (roots.Count > 1) throw new InvalidOperationException("More than one root specified"); - - var emptyElement = new Element(new ElementHandle("nullRoot", new NullRootEntityToken())); - emptyElement.VisualData = new ElementVisualizedData { HasChildren = false, Label = "nullroot", Icon = CommonElementIcons.Folder }; - - roots.Clear(); - roots.Add(emptyElement); - } - else if (roots.Count > 1) - { - throw new InvalidOperationException("More than one root specified"); - } - - return roots[0].GetClientElement(); - } - - - - /// - public static List GetRoots(string providerHandle, string serializedSearchToken) - { - SearchToken searchToken = null; - if (!string.IsNullOrEmpty(serializedSearchToken)) - { - searchToken = SearchToken.Deserialize(serializedSearchToken); - } - - List roots; - if (UserSettings.ForeignLocaleCultureInfo == null || UserSettings.ForeignLocaleCultureInfo.Equals(UserSettings.ActiveLocaleCultureInfo)) - { - roots = ElementFacade.GetRoots(new ElementProviderHandle(providerHandle), searchToken).ToList(); - } - else - { - roots = ElementFacade.GetForeignRoots(new ElementProviderHandle(providerHandle), searchToken).ToList(); - } - - return roots.ToClientElementList(); - } - - - - /// - public static List GetLocaleAwarePerspectiveElements() - { - IEnumerable elements = ElementFacade.GetPerspectiveElements(true); - - List clientElementKeys = new List(); - foreach (Element element in elements) - { - if (element.IsLocaleAware) - { - clientElementKeys.Add(element.GetClientElement().ElementKey); - } - } - - return clientElementKeys; - } - - - - /// - public static List GetPerspectiveElementsWithNoSecurity() - { - return ElementFacade.GetPerspectiveElementsWithNoSecurity().ToList().ToClientElementList(); - } - - - - /// - public static List GetChildren(string providerName, string serializedEntityToken, string piggybag, string serializedSearchToken) - { - //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", "----- Start -----------------------------------------------"); - - int t1 = Environment.TickCount; - - EntityToken entityToken = EntityTokenSerializer.Deserialize(serializedEntityToken); - ElementHandle elementHandle = new ElementHandle(providerName, entityToken, piggybag); - - //int t2 = Environment.TickCount; - - SearchToken searchToken = null; - if (!string.IsNullOrEmpty(serializedSearchToken)) - { - searchToken = SearchToken.Deserialize(serializedSearchToken); - } - - List childElements; - if (UserSettings.ForeignLocaleCultureInfo == null || UserSettings.ForeignLocaleCultureInfo.Equals(UserSettings.ActiveLocaleCultureInfo)) - { - childElements = ElementFacade.GetChildren(elementHandle, searchToken).ToList(); - } - else - { - childElements = ElementFacade.GetForeignChildren(elementHandle, searchToken).ToList(); - } - - //int t3 = Environment.TickCount; - - List resultList = childElements.ToClientElementList(); - - int t4 = Environment.TickCount; - - //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", string.Format("ElementHandle: {0} ms", t2 - t1)); - //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", string.Format("GetChildren: {0} ms", t3 - t2)); - //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", string.Format("ToClientElementList: {0} ms", t4 - t3)); - //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", string.Format("Total: {0} ms", t4 - t1)); - //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", "----- End -------------------------------------------------"); - - //LoggingService.LogVerbose("TreeServiceFacade", string.Format("GetChildren: {0} ms", t4 - t1)); - - return resultList; - } - - - - /// - public static List GetMultipleChildren(List nodesToBeRefreshed) - { - int t1 = Environment.TickCount; - - var result = new List(); - - foreach (RefreshChildrenParams node in nodesToBeRefreshed) - { - EntityToken entityToken; - - try - { - entityToken = EntityTokenSerializer.Deserialize(node.EntityToken); - } - catch (EntityTokenSerializerException) - { - continue; - } - - var elementHandle = new ElementHandle(node.ProviderName, entityToken, node.Piggybag); - SearchToken searchToken = null; - if (!string.IsNullOrEmpty(node.SearchToken)) - { - searchToken = SearchToken.Deserialize(node.SearchToken); - } - - List childElements; - if (UserSettings.ForeignLocaleCultureInfo == null || UserSettings.ForeignLocaleCultureInfo.Equals(UserSettings.ActiveLocaleCultureInfo)) - { - childElements = ElementFacade.GetChildren(elementHandle, searchToken).ToList(); - } - else - { - childElements = ElementFacade.GetForeignChildren(elementHandle, searchToken).ToList(); - } - - result.Add(new RefreshChildrenInfo - { - ElementKey = GetElementKey(node.ProviderName, node.EntityToken, node.Piggybag), - ClientElements = childElements.ToClientElementList() - }); - } - - int t2 = Environment.TickCount; - - //LoggingService.LogVerbose("TreeServiceFacade", string.Format("GetMultipleChildren: {0} ms", t2 - t1)); - - return result; - } - - - - /// - public static List GetLabeledProperties(string providerName, string serializedEntityToken, string piggybag) - { - var elementEntityToken = EntityTokenSerializer.Deserialize(serializedEntityToken); - var elementHandle = new ElementHandle(providerName, elementEntityToken, piggybag); - - bool showForeign = UserSettings.ForeignLocaleCultureInfo != null - && UserSettings.ForeignLocaleCultureInfo.Equals(UserSettings.ActiveLocaleCultureInfo); - - var labeledProperties = showForeign - ? ElementFacade.GetForeignLabeledProperties(elementHandle) - : ElementFacade.GetLabeledProperties(elementHandle); - - return - (from property in labeledProperties - select new ClientLabeledProperty(property)).ToList(); - } - - - - /// - public static void ExecuteElementAction(string providerName, string serializedEntityToken, string piggybag, string serializedActionToken, string consoleId) - { - using (DebugLoggingScope.MethodInfoScope) - { - EntityToken entityToken = EntityTokenSerializer.Deserialize(serializedEntityToken); - if (!entityToken.IsValid()) - { - ShowInvalidEntityMessage(consoleId); - return; - } - - var elementHandle = new ElementHandle(providerName, entityToken, piggybag); - - ActionToken actionToken = ActionTokenSerializer.Deserialize(serializedActionToken, true); - ActionHandle actionHandle = new ActionHandle(actionToken); - - ActionExecutionMediator.ExecuteElementAction(elementHandle, actionHandle, consoleId); - } - } - - - - /// - public static bool ExecuteElementDraggedAndDropped(string draggedElementProviderName, string draggedElementSerializedEntityToken, string draggedElementPiggybag, string newParentElementProviderName, string newParentElementSerializedEntityToken, string newParentElementPiggybag, int dropIndex, string consoleId, bool isCopy) - { - if (draggedElementProviderName != newParentElementProviderName) - { - throw new InvalidOperationException("Only drag'n'drop internal in element providers are allowed"); - } - - EntityToken draggedElementEntityToken = EntityTokenSerializer.Deserialize(draggedElementSerializedEntityToken); - ElementHandle draggedElementHandle = new ElementHandle(draggedElementProviderName, draggedElementEntityToken, draggedElementPiggybag); - - EntityToken newParentElementEntityToken = EntityTokenSerializer.Deserialize(newParentElementSerializedEntityToken); - ElementHandle newParentdElementHandle = new ElementHandle(newParentElementProviderName, newParentElementEntityToken, newParentElementPiggybag); - - return ActionExecutionMediator.ExecuteElementDraggedAndDropped(draggedElementHandle, newParentdElementHandle, dropIndex, consoleId, isCopy); - } - - - /// - public static List FindEntityToken(string serializedAncestorEntityToken, string serializedEntityToken, List openedNodes) - { - Verify.ArgumentNotNullOrEmpty(serializedAncestorEntityToken, "serializedAncestorEntityToken"); - Verify.ArgumentNotNullOrEmpty(serializedEntityToken, "serializedEntityToken"); - - EntityToken ancestorEntityToken = EntityTokenSerializer.Deserialize(serializedAncestorEntityToken); - EntityToken entityToken = EntityTokenSerializer.Deserialize(serializedEntityToken); - - return FindEntityToken(ancestorEntityToken, entityToken, openedNodes); - } - - - internal static List FindEntityToken(EntityToken ancestorEntityToken, EntityToken entityToken, List nodesToRefresh) - { - var openedNodes = nodesToRefresh.Select(node => new - { - EntityToken = EntityTokenSerializer.Deserialize(node.EntityToken), - ElementData = node - }).ToList(); - - foreach (List ancestorChain in GetAncestorChains(ancestorEntityToken, entityToken)) - { - if (ancestorChain == null || ancestorChain.Count == 0) - { - continue; - } - - List ancestorEntityTokens = ancestorChain.ToList(); - - int lastAlreadyOpenedNodeIndex = 0; - while (lastAlreadyOpenedNodeIndex + 1 < ancestorChain.Count - && openedNodes.Any(node => node.EntityToken.Equals(ancestorEntityTokens[lastAlreadyOpenedNodeIndex + 1]))) - { - lastAlreadyOpenedNodeIndex++; - } - - var openNode = openedNodes.FirstOrDefault(node => node.EntityToken.Equals(ancestorEntityTokens[lastAlreadyOpenedNodeIndex])); - if (openNode == null) - { - return null; - } - - // Expanding all the nodes under the root - var nodesToBeExpanded = new List(); - nodesToBeExpanded.AddRange(ancestorEntityTokens.Skip(lastAlreadyOpenedNodeIndex)); - nodesToBeExpanded.RemoveAt(nodesToBeExpanded.Count - 1); - // Last node is a target one, so doesn't have to be expanded - nodesToBeExpanded.AddRange(openedNodes.Select(node => node.EntityToken)); - - var result = new List(); - // Expanding all the nodes, and checking if all of the nodes in the ancestor chain is marked - // as seen in TreeLockBehaviour - bool success = - ExpandNodesRec(openNode.ElementData.EntityToken, - openNode.ElementData.ProviderName, - openNode.ElementData.Piggybag, - nodesToBeExpanded, - result, - ancestorEntityTokens); - - if (success) - { - return result; - } - } - return null; - } - - /// - /// Expands nodes recurcively. - /// - /// - /// - /// - /// - /// - /// - /// Returns false, if there's a key node, that has [element.TreeLockBehavior == None] - private static bool ExpandNodesRec(string entityToken, string elementProviderName, string piggybag, - List entityTokensToBeExpanded, List resultList, List keyNodes) - { - if (resultList.Count > 1000) // Preventing an infinite loop - { - return true; - } - List children = GetChildren(elementProviderName, entityToken, piggybag, null); - - var refreshChildrenInfo = new RefreshChildrenInfo - { - ElementKey = GetElementKey(elementProviderName, entityToken, piggybag), - ClientElements = children - }; - resultList.Add(refreshChildrenInfo); - - foreach (ClientElement child in children) - { - var childEntityToken = EntityTokenSerializer.Deserialize(child.EntityToken); - - if (!child.TreeLockEnabled - && keyNodes.Contains(childEntityToken)) - { - return false; - } - - if (entityTokensToBeExpanded.Contains(childEntityToken)) - { - if (ExpandNodesRec(child.EntityToken, - child.ProviderName, - child.Piggybag, - entityTokensToBeExpanded, - resultList, - keyNodes)) - { - return true; - } - } - else - { - if (keyNodes.Contains(childEntityToken)) - { - return true; - } - } - } - - return false; - } - - - - // TODO: Move logic to another place - private static string GetElementKey(string providerName, string entityToken, string piggybag) - { - return providerName + entityToken + piggybag; - } - - - - private static IEnumerable> GetAncestorChains(EntityToken ancestorEnitityToken, EntityToken entityToken) - { - foreach (List ancestorChain in GetAncestorChains(entityToken, 20)) - { - if (ancestorChain.Count > 1) - { - int index = ancestorChain.IndexOf(ancestorEnitityToken); - if(index < 0) continue; - - yield return (index == 0) ? ancestorChain : ancestorChain.GetRange(index, ancestorChain.Count - index); - } - } - } - - - - private static IEnumerable> GetAncestorChains(EntityToken descendant, int deep, List visitedParents = null) - { - if (deep == 0) - { - yield return new List(); - yield break; - } - - if (visitedParents == null) - { - visitedParents = new List(); - } - visitedParents.Add(descendant); - - List parents = ParentsFacade.GetAllParents(descendant); - if (parents.Count == 0) - { - var newChain = new List {descendant}; - yield return newChain; - yield break; - } - - // NOTE: A workaround which gives "AllFunctionElementProvider" search results less priority that other function element providers - if (parents.Count == 2 && parents[0].Id != null && parents[0].Id.StartsWith("ROOT:AllFunctionsElementProvider")) - { - parents.Reverse(); - } - - foreach (var parent in parents) - { - foreach (List chain in GetAncestorChains(parent, deep - 1, visitedParents)) - { - chain.Add(descendant); - yield return chain; - } - } - } - - - - private static void ShowInvalidEntityMessage(string consoleId) - { - // TODO: Add tree refreshing, localize message - var msgBoxEntry = new MessageBoxMessageQueueItem { DialogType = DialogType.Error, Title = "Data item not found", Message = "This item seems to have been deleted.\n\nPlease update the tree by using the context menu \"Refresh\" command." }; - ConsoleMessageQueueFacade.Enqueue(msgBoxEntry, consoleId); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Composite.C1Console.Events; +using Composite.C1Console.Elements; +using Composite.Core.Logging; +using Composite.Core.ResourceSystem.Icons; +using Composite.C1Console.Security; +using Composite.C1Console.Users; +using Composite.Core.WebClient.Services.TreeServiceObjects; +using Composite.Core.WebClient.Services.TreeServiceObjects.ExtensionMethods; + + +namespace Composite.Core.WebClient.FlowMediators +{ + internal class NullRootEntityToken : EntityToken + { + public override string Type { get { return "null"; } } + public override string Source { get { return "null"; } } + public override string Id { get { return "null"; } } + public override string Serialize() { return "NullRootEntiryToken"; } + } + + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public static class TreeServicesFacade + { + /// + public static ClientElement GetRoot() + { + List roots = ElementFacade.GetRoots(null).ToList(); + + if (roots.Count == 0) + { + // user with out any access logging in - return "empty root" + roots = ElementFacade.GetRootsWithNoSecurity().ToList(); + if (roots.Count == 0) throw new InvalidOperationException("No roots specified"); + if (roots.Count > 1) throw new InvalidOperationException("More than one root specified"); + + var emptyElement = new Element(new ElementHandle("nullRoot", new NullRootEntityToken())); + emptyElement.VisualData = new ElementVisualizedData { HasChildren = false, Label = "nullroot", Icon = CommonElementIcons.Folder }; + + roots.Clear(); + roots.Add(emptyElement); + } + else if (roots.Count > 1) + { + throw new InvalidOperationException("More than one root specified"); + } + + return roots[0].GetClientElement(); + } + + + + /// + public static List GetRoots(string providerHandle, string serializedSearchToken) + { + SearchToken searchToken = null; + if (!string.IsNullOrEmpty(serializedSearchToken)) + { + searchToken = SearchToken.Deserialize(serializedSearchToken); + } + + List roots; + if (UserSettings.ForeignLocaleCultureInfo == null || UserSettings.ForeignLocaleCultureInfo.Equals(UserSettings.ActiveLocaleCultureInfo)) + { + roots = ElementFacade.GetRoots(new ElementProviderHandle(providerHandle), searchToken).ToList(); + } + else + { + roots = ElementFacade.GetForeignRoots(new ElementProviderHandle(providerHandle), searchToken).ToList(); + } + + return roots.ToClientElementList(); + } + + + + /// + public static List GetLocaleAwarePerspectiveElements() + { + IEnumerable elements = ElementFacade.GetPerspectiveElements(true); + + List clientElementKeys = new List(); + foreach (Element element in elements) + { + if (element.IsLocaleAware) + { + clientElementKeys.Add(element.GetClientElement().ElementKey); + } + } + + return clientElementKeys; + } + + + + /// + public static List GetPerspectiveElementsWithNoSecurity() + { + return ElementFacade.GetPerspectiveElementsWithNoSecurity().ToList().ToClientElementList(); + } + + + + /// + public static List GetChildren(string providerName, string serializedEntityToken, string piggybag, string serializedSearchToken) + { + //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", "----- Start -----------------------------------------------"); + + int t1 = Environment.TickCount; + + EntityToken entityToken = EntityTokenSerializer.Deserialize(serializedEntityToken); + ElementHandle elementHandle = new ElementHandle(providerName, entityToken, piggybag); + + //int t2 = Environment.TickCount; + + SearchToken searchToken = null; + if (!string.IsNullOrEmpty(serializedSearchToken)) + { + searchToken = SearchToken.Deserialize(serializedSearchToken); + } + + List childElements; + if (UserSettings.ForeignLocaleCultureInfo == null || UserSettings.ForeignLocaleCultureInfo.Equals(UserSettings.ActiveLocaleCultureInfo)) + { + childElements = ElementFacade.GetChildren(elementHandle, searchToken).ToList(); + } + else + { + childElements = ElementFacade.GetForeignChildren(elementHandle, searchToken).ToList(); + } + + //int t3 = Environment.TickCount; + + List resultList = childElements.ToClientElementList(); + + int t4 = Environment.TickCount; + + //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", string.Format("ElementHandle: {0} ms", t2 - t1)); + //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", string.Format("GetChildren: {0} ms", t3 - t2)); + //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", string.Format("ToClientElementList: {0} ms", t4 - t3)); + //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", string.Format("Total: {0} ms", t4 - t1)); + //LoggingService.LogVerbose("RGB(255, 0, 255)TreeServiceFacade", "----- End -------------------------------------------------"); + + //LoggingService.LogVerbose("TreeServiceFacade", string.Format("GetChildren: {0} ms", t4 - t1)); + + return resultList; + } + + + + /// + public static List GetMultipleChildren(List nodesToBeRefreshed) + { + int t1 = Environment.TickCount; + + var result = new List(); + + foreach (RefreshChildrenParams node in nodesToBeRefreshed) + { + EntityToken entityToken; + + try + { + entityToken = EntityTokenSerializer.Deserialize(node.EntityToken); + } + catch (EntityTokenSerializerException) + { + continue; + } + + var elementHandle = new ElementHandle(node.ProviderName, entityToken, node.Piggybag); + SearchToken searchToken = null; + if (!string.IsNullOrEmpty(node.SearchToken)) + { + searchToken = SearchToken.Deserialize(node.SearchToken); + } + + List childElements; + if (UserSettings.ForeignLocaleCultureInfo == null || UserSettings.ForeignLocaleCultureInfo.Equals(UserSettings.ActiveLocaleCultureInfo)) + { + childElements = ElementFacade.GetChildren(elementHandle, searchToken).ToList(); + } + else + { + childElements = ElementFacade.GetForeignChildren(elementHandle, searchToken).ToList(); + } + + result.Add(new RefreshChildrenInfo + { + ElementKey = GetElementKey(node.ProviderName, node.EntityToken, node.Piggybag), + ClientElements = childElements.ToClientElementList() + }); + } + + int t2 = Environment.TickCount; + + //LoggingService.LogVerbose("TreeServiceFacade", string.Format("GetMultipleChildren: {0} ms", t2 - t1)); + + return result; + } + + + + /// + public static List GetLabeledProperties(string providerName, string serializedEntityToken, string piggybag) + { + var elementEntityToken = EntityTokenSerializer.Deserialize(serializedEntityToken); + var elementHandle = new ElementHandle(providerName, elementEntityToken, piggybag); + + bool showForeign = UserSettings.ForeignLocaleCultureInfo != null + && UserSettings.ForeignLocaleCultureInfo.Equals(UserSettings.ActiveLocaleCultureInfo); + + var labeledProperties = showForeign + ? ElementFacade.GetForeignLabeledProperties(elementHandle) + : ElementFacade.GetLabeledProperties(elementHandle); + + return + (from property in labeledProperties + select new ClientLabeledProperty(property)).ToList(); + } + + + + /// + public static void ExecuteElementAction(string providerName, string serializedEntityToken, string piggybag, string serializedActionToken, string consoleId) + { + using (DebugLoggingScope.MethodInfoScope) + { + EntityToken entityToken = EntityTokenSerializer.Deserialize(serializedEntityToken); + if (!entityToken.IsValid()) + { + ShowInvalidEntityMessage(consoleId); + return; + } + + var elementHandle = new ElementHandle(providerName, entityToken, piggybag); + + ActionToken actionToken = ActionTokenSerializer.Deserialize(serializedActionToken, true); + ActionHandle actionHandle = new ActionHandle(actionToken); + + ActionExecutionMediator.ExecuteElementAction(elementHandle, actionHandle, consoleId); + } + } + + + + /// + public static bool ExecuteElementDraggedAndDropped(string draggedElementProviderName, string draggedElementSerializedEntityToken, string draggedElementPiggybag, string newParentElementProviderName, string newParentElementSerializedEntityToken, string newParentElementPiggybag, int dropIndex, string consoleId, bool isCopy) + { + if (draggedElementProviderName != newParentElementProviderName) + { + throw new InvalidOperationException("Only drag'n'drop internal in element providers are allowed"); + } + + EntityToken draggedElementEntityToken = EntityTokenSerializer.Deserialize(draggedElementSerializedEntityToken); + ElementHandle draggedElementHandle = new ElementHandle(draggedElementProviderName, draggedElementEntityToken, draggedElementPiggybag); + + EntityToken newParentElementEntityToken = EntityTokenSerializer.Deserialize(newParentElementSerializedEntityToken); + ElementHandle newParentdElementHandle = new ElementHandle(newParentElementProviderName, newParentElementEntityToken, newParentElementPiggybag); + + return ActionExecutionMediator.ExecuteElementDraggedAndDropped(draggedElementHandle, newParentdElementHandle, dropIndex, consoleId, isCopy); + } + + + /// + public static List FindEntityToken(string serializedAncestorEntityToken, string serializedEntityToken, List openedNodes) + { + Verify.ArgumentNotNullOrEmpty(serializedAncestorEntityToken, "serializedAncestorEntityToken"); + Verify.ArgumentNotNullOrEmpty(serializedEntityToken, "serializedEntityToken"); + + EntityToken ancestorEntityToken = EntityTokenSerializer.Deserialize(serializedAncestorEntityToken); + EntityToken entityToken = EntityTokenSerializer.Deserialize(serializedEntityToken); + + return FindEntityToken(ancestorEntityToken, entityToken, openedNodes); + } + + + internal static List FindEntityToken(EntityToken ancestorEntityToken, EntityToken entityToken, List nodesToRefresh) + { + var openedNodes = nodesToRefresh.Select(node => new + { + EntityToken = EntityTokenSerializer.Deserialize(node.EntityToken), + ElementData = node + }).ToList(); + + foreach (List ancestorChain in GetAncestorChains(ancestorEntityToken, entityToken)) + { + if (ancestorChain == null || ancestorChain.Count == 0) + { + continue; + } + + List ancestorEntityTokens = ancestorChain.ToList(); + + int lastAlreadyOpenedNodeIndex = 0; + while (lastAlreadyOpenedNodeIndex + 1 < ancestorChain.Count + && openedNodes.Any(node => node.EntityToken.Equals(ancestorEntityTokens[lastAlreadyOpenedNodeIndex + 1]))) + { + lastAlreadyOpenedNodeIndex++; + } + + var openNode = openedNodes.FirstOrDefault(node => node.EntityToken.Equals(ancestorEntityTokens[lastAlreadyOpenedNodeIndex])); + if (openNode == null) + { + return null; + } + + // Expanding all the nodes under the root + var nodesToBeExpanded = new List(); + nodesToBeExpanded.AddRange(ancestorEntityTokens.Skip(lastAlreadyOpenedNodeIndex)); + nodesToBeExpanded.RemoveAt(nodesToBeExpanded.Count - 1); + // Last node is a target one, so doesn't have to be expanded + nodesToBeExpanded.AddRange(openedNodes.Select(node => node.EntityToken)); + + var result = new List(); + // Expanding all the nodes, and checking if all of the nodes in the ancestor chain is marked + // as seen in TreeLockBehaviour + bool success = + ExpandNodesRec(openNode.ElementData.EntityToken, + openNode.ElementData.ProviderName, + openNode.ElementData.Piggybag, + nodesToBeExpanded, + result, + ancestorEntityTokens); + + if (success) + { + return result; + } + } + return null; + } + + /// + /// Expands nodes recurcively. + /// + /// + /// + /// + /// + /// + /// + /// Returns false, if there's a key node, that has [element.TreeLockBehavior == None] + private static bool ExpandNodesRec(string entityToken, string elementProviderName, string piggybag, + List entityTokensToBeExpanded, List resultList, List keyNodes) + { + if (resultList.Count > 1000) // Preventing an infinite loop + { + return true; + } + List children = GetChildren(elementProviderName, entityToken, piggybag, null); + + var refreshChildrenInfo = new RefreshChildrenInfo + { + ElementKey = GetElementKey(elementProviderName, entityToken, piggybag), + ClientElements = children + }; + resultList.Add(refreshChildrenInfo); + + foreach (ClientElement child in children) + { + var childEntityToken = EntityTokenSerializer.Deserialize(child.EntityToken); + + if (!child.TreeLockEnabled + && keyNodes.Contains(childEntityToken)) + { + return false; + } + + if (entityTokensToBeExpanded.Contains(childEntityToken)) + { + if (ExpandNodesRec(child.EntityToken, + child.ProviderName, + child.Piggybag, + entityTokensToBeExpanded, + resultList, + keyNodes)) + { + return true; + } + } + else + { + if (keyNodes.Contains(childEntityToken)) + { + return true; + } + } + } + + return false; + } + + + + // TODO: Move logic to another place + private static string GetElementKey(string providerName, string entityToken, string piggybag) + { + return providerName + entityToken + piggybag; + } + + + + private static IEnumerable> GetAncestorChains(EntityToken ancestorEnitityToken, EntityToken entityToken) + { + foreach (List ancestorChain in GetAncestorChains(entityToken, 20)) + { + if (ancestorChain.Count > 1) + { + int index = ancestorChain.IndexOf(ancestorEnitityToken); + if(index < 0) continue; + + yield return (index == 0) ? ancestorChain : ancestorChain.GetRange(index, ancestorChain.Count - index); + } + } + } + + + + private static IEnumerable> GetAncestorChains(EntityToken descendant, int deep, List visitedParents = null) + { + if (deep == 0) + { + yield return new List(); + yield break; + } + + if (visitedParents == null) + { + visitedParents = new List(); + } + visitedParents.Add(descendant); + + List parents = ParentsFacade.GetAllParents(descendant); + if (parents.Count == 0) + { + var newChain = new List {descendant}; + yield return newChain; + yield break; + } + + // NOTE: A workaround which gives "AllFunctionElementProvider" search results less priority that other function element providers + if (parents.Count == 2 && parents[0].Id != null && parents[0].Id.StartsWith("ROOT:AllFunctionsElementProvider")) + { + parents.Reverse(); + } + + foreach (var parent in parents) + { + foreach (List chain in GetAncestorChains(parent, deep - 1, visitedParents)) + { + chain.Add(descendant); + yield return chain; + } + } + } + + + + private static void ShowInvalidEntityMessage(string consoleId) + { + // TODO: Add tree refreshing, localize message + var msgBoxEntry = new MessageBoxMessageQueueItem { DialogType = DialogType.Error, Title = "Data item not found", Message = "This item seems to have been deleted.\n\nPlease update the tree by using the context menu \"Refresh\" command." }; + ConsoleMessageQueueFacade.Enqueue(msgBoxEntry, consoleId); + } + } +} diff --git a/Composite/Core/WebClient/Renderings/Page/PageStructureInfo.cs b/Composite/Core/WebClient/Renderings/Page/PageStructureInfo.cs index 87e05cf83a..671452ae86 100644 --- a/Composite/Core/WebClient/Renderings/Page/PageStructureInfo.cs +++ b/Composite/Core/WebClient/Renderings/Page/PageStructureInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -96,7 +96,6 @@ public static IEnumerable> PageListInDocumentOrder() return PageListInDocumentOrder(GetSiteMap(), 0); } - private static IEnumerable> PageListInDocumentOrder(IEnumerable pageElements, int indentLevel) { var indentString = new string(' ', indentLevel); @@ -105,8 +104,8 @@ private static IEnumerable> PageListInDocumentOrder(I foreach (XElement pageElement in pageElements) { string label = GetLabelForPageElement(indentString, pageElement); - string id = pageElement.Attribute(AttributeNames.Id).Value; - yield return new KeyValuePair(new Guid(id), label); + var id = GetIdForPageElement(pageElement); + yield return new KeyValuePair(id, label); foreach (KeyValuePair childOption in PageListInDocumentOrder(pageElement.Elements(), indentLevel + 1)) { @@ -115,7 +114,13 @@ private static IEnumerable> PageListInDocumentOrder(I } } - private static string GetLabelForPageElement(string indentString, XElement pageElement) + internal static Guid GetIdForPageElement(XElement pageElement) + { + string id = pageElement.Attribute(AttributeNames.Id).Value; + return Guid.Parse(id); + } + + internal static string GetLabelForPageElement(string indentString, XElement pageElement) { string labelText = (pageElement.Attribute(AttributeNames.MenuTitle) ?? pageElement.Attribute(AttributeNames.Title)).Value; diff --git a/Composite/Data/Types/PageServices.cs b/Composite/Data/Types/PageServices.cs index a2208a6e9f..656e278078 100644 --- a/Composite/Data/Types/PageServices.cs +++ b/Composite/Data/Types/PageServices.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using Composite.C1Console.Trees; @@ -90,6 +91,14 @@ public static int GetChildrenCount(Guid parentId) } } + /// + public static ReadOnlyCollection GetChildrenIDs(Guid parentId) + { + using (new DataScope(DataScopeIdentifier.Administrated)) + { + return PageManager.GetChildrenIDs(parentId); + } + } /// public static bool IsChildrenAlphabeticOrdered(Guid parentId) diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index ae684b94a0..e6d04fc667 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -122,10 +122,12 @@ public IEnumerable GetRoots(SearchToken searchToken) yield break; } - int pages; - using (new DataScope(DataScopeIdentifier.Administrated)) + ICollection pageIds = PageServices.GetChildrenIDs(Guid.Empty); + + var homePageIdFilter = (searchToken as PageSearchToken)?.HomePageId ?? Guid.Empty; + if (homePageIdFilter != Guid.Empty) { - pages = PageServices.GetChildrenCount(Guid.Empty); + pageIds = pageIds.Where(p => p == homePageIdFilter).ToList(); } var dragAndDropInfo = new ElementDragAndDropInfo(); @@ -138,7 +140,7 @@ public IEnumerable GetRoots(SearchToken searchToken) { Label = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.RootLabel"), ToolTip = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.RootLabelToolTip"), - HasChildren = pages != 0, + HasChildren = pageIds.Count != 0, Icon = PageElementProvider.RootClosed, OpenedIcon = PageElementProvider.RootOpen } @@ -482,18 +484,38 @@ private IEnumerable GetChildElements(EntityToken entityToken, IEnumerab private IEnumerable GetChildrenPages(EntityToken entityToken, SearchToken searchToken) { - Guid? itemId = GetParentPageId(entityToken); + var itemId = GetParentPageId(entityToken); - if (!itemId.HasValue) return new IPage[] { }; + if (!itemId.HasValue) + return Enumerable.Empty(); + var parentPageId = itemId.Value; + + ICollection pages; + if (searchToken.IsValidKeyword()) + { + var keyword = searchToken.Keyword.ToLowerInvariant(); + pages = GetPagesByKeyword(keyword, parentPageId); + } + else + { + pages = PageServices.GetChildren(parentPageId).Evaluate(); + } - if (!searchToken.IsValidKeyword()) + if (parentPageId == Guid.Empty) { - return OrderByVersions(PageServices.GetChildren(itemId.Value).Evaluate()); + var homePageIdFilter = (searchToken as PageSearchToken)?.HomePageId ?? Guid.Empty; + if (homePageIdFilter != Guid.Empty) + { + pages = pages.Where(p => p.Id == homePageIdFilter).ToList(); + } } - string keyword = searchToken.Keyword.ToLowerInvariant(); + return OrderByVersions(pages); + } + private ICollection GetPagesByKeyword(string keyword, Guid parentId) + { var predicateItems = from page in DataFacade.GetData() where (page.Description != null && page.Description.ToLowerInvariant().Contains(keyword)) || @@ -510,7 +532,7 @@ from page in DataFacade.GetData() nodes = nodes.Concat(GetAncestorPath(node, keyTree)).ToList(); } - List pageIds = nodes.Where(x => x.ParentKey == itemId).Select(x => x.Key).Distinct().ToList(); + List pageIds = nodes.Where(x => x.ParentKey == parentId).Select(x => x.Key).Distinct().ToList(); var pages = new List(); @@ -522,7 +544,7 @@ from page in DataFacade.GetData() } } - return OrderByVersions(pages); + return pages; } private IEnumerable OrderByVersions(IEnumerable pages) diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageSearchToken.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageSearchToken.cs new file mode 100644 index 0000000000..7f086345bf --- /dev/null +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageSearchToken.cs @@ -0,0 +1,15 @@ +using System; +using Composite.C1Console.Elements; + +namespace Composite.Plugins.Elements.ElementProviders.PageElementProvider +{ + /// + /// + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public class PageSearchToken : SearchToken + { + /// + public Guid? HomePageId { get; set; } + } +} diff --git a/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs b/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs index 3e95ed77af..991230a23f 100644 --- a/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs +++ b/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs @@ -1,14 +1,16 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Xml.Linq; +using System.Web; +using Composite.C1Console.Actions; +using Composite.C1Console.Workflow.Activities; using Composite.Functions; -using Composite.C1Console.Security; using Composite.Plugins.Functions.FunctionProviders.StandardFunctionProvider.Foundation; -using Composite.Core.ResourceSystem; +using Composite.Core.Routing.Pages; +using Composite.Core.WebClient.FlowMediators.FormFlowRendering; using Composite.Core.WebClient.Renderings.Page; using Composite.Data; +using Composite.Data.Types; namespace Composite.Plugins.Functions.FunctionProviders.StandardFunctionProvider.Pages { @@ -21,32 +23,78 @@ public GetPageIdFunction(EntityTokenFactory entityTokenFactory) public override object Execute(ParameterList parameters, FunctionContextContainer context) { - SitemapScope SitemapScope; - if (parameters.TryGetParameter("SitemapScope", out SitemapScope) == false) + if (parameters.TryGetParameter("SitemapScope", out var sitemapScope) == false) { - SitemapScope = SitemapScope.Current; + sitemapScope = SitemapScope.Current; } - Guid pageId = Guid.Empty; + var pageId = GetCurrentPageId(); - switch (SitemapScope) + switch (sitemapScope) { case SitemapScope.Current: - pageId = PageRenderer.CurrentPageId; - break; + return pageId; case SitemapScope.Parent: case SitemapScope.Level1: case SitemapScope.Level2: case SitemapScope.Level3: case SitemapScope.Level4: - IEnumerable pageIds = PageStructureInfo.GetAssociatedPageIds(PageRenderer.CurrentPageId, SitemapScope); - pageId = pageIds.FirstOrDefault(); - break; + var pageIds = PageStructureInfo.GetAssociatedPageIds(pageId, sitemapScope); + return pageIds.FirstOrDefault(); default: - throw new NotImplementedException("Unhandled SitemapScope type: " + SitemapScope.ToString()); + throw new NotImplementedException("Unhandled SitemapScope type: " + sitemapScope.ToString()); } + } + + private Guid GetCurrentPageId() + { + return GetCurrentPageIdFromPageRenderer() + ?? GetCurrentPageIdFromPageUrlData() + ?? GetCurrentPageIdFromHttpContext() + ?? GetCurrentPageIdFromFormFlow() + ?? Guid.Empty; + } + + private Guid? GetCurrentPageIdFromPageRenderer() + { + var pageId = PageRenderer.CurrentPageId; + return pageId == Guid.Empty ? (Guid?)null : pageId; + } + + private Guid? GetCurrentPageIdFromPageUrlData() + { + var pageId = C1PageRoute.PageUrlData?.PageId; + return pageId == Guid.Empty ? null : pageId; + } + + private Guid? GetCurrentPageIdFromHttpContext() + { + var entityToken = HttpContext.Current?.Items[ActionExecutorFacade.HttpContextItem_EntityToken]; + return GetPageIdFromDataToken(entityToken as DataEntityToken); + } + + private Guid? GetCurrentPageIdFromFormFlow() + { + var currentFormTreeCompiler = FormFlowUiDefinitionRenderer.CurrentFormTreeCompiler; + if (currentFormTreeCompiler.BindingObjects.TryGetValue(FormsWorkflow.EntityTokenKey, out var entityToken)) + { + return GetPageIdFromDataToken(entityToken as DataEntityToken); + } + + return null; + } + + private Guid? GetPageIdFromDataToken(DataEntityToken entityToken) + { + if (entityToken == null) + return null; + + if (Type.GetType(entityToken.Type, throwOnError: false) != typeof(IPage)) + return null; - return pageId; + var data = entityToken.Data as IPage; + var pageId = data?.Id; + return pageId == Guid.Empty ? null : pageId; } diff --git a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/HomePageSelectorWidgetFunction.cs b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/HomePageSelectorWidgetFunction.cs new file mode 100644 index 0000000000..ef140b730c --- /dev/null +++ b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/HomePageSelectorWidgetFunction.cs @@ -0,0 +1,60 @@ +using System.Collections; +using System.Xml.Linq; +using Composite.Core.WebClient.Renderings.Page; +using Composite.Data; +using Composite.Data.Types; +using Composite.Functions; +using Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.Foundation; + +namespace Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.DataReference +{ + /// + /// + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public sealed class HomePageSelectorWidgetFunction : CompositeWidgetFunctionBase + { + /// + public HomePageSelectorWidgetFunction(EntityTokenFactory entityTokenFactory) + : base(CompositeName, typeof(DataReference), entityTokenFactory) + { + } + + private const string CompositeName = CommonNamespace + ".DataReference" + ".HomePageSelector"; + + /// + public override XElement GetWidgetMarkup(ParameterList parameters, string label, HelpDefinition helpDefinition, string bindingSourceName) + { + return StandardWidgetFunctions.BuildStaticCallPopulatedSelectorFormsMarkup( + parameters: parameters, + label: label, + helpDefinition: helpDefinition, + bindingSourceName: bindingSourceName, + optionsGeneratingStaticType: typeof(HomePageSelectorWidgetFunction), + optionsGeneratingStaticMethodName: nameof(GetHomePages), + optionsGeneratingStaticMethodParameterValue: null, + optionsObjectKeyPropertyName: "Key", + optionsObjectLabelPropertyName: "Label", + multiSelect: false, + compactMode: true, + required: true, + bindToString: false); + } + + /// + /// To be called through reflection + /// + /// + public static IEnumerable GetHomePages() + { + foreach (var element in PageStructureInfo.GetSiteMap()) + { + yield return new + { + Key = PageStructureInfo.GetIdForPageElement(element), + Label = PageStructureInfo.GetLabelForPageElement("", element) + }; + } + } + } +} \ No newline at end of file diff --git a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/NullablePageReferenceSelectorWidgetFunction.cs b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/NullablePageReferenceSelectorWidgetFunction.cs index 698881a14f..49e1e34ed0 100644 --- a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/NullablePageReferenceSelectorWidgetFunction.cs +++ b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/NullablePageReferenceSelectorWidgetFunction.cs @@ -1,45 +1,24 @@ -using System; -using System.Xml.Linq; +using System.Collections.Generic; +using Composite.C1Console.Elements; using Composite.Data; -using Composite.Functions; using Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.Foundation; -using Composite.Core.Types; using Composite.Data.Types; namespace Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.DataReference { - internal sealed class NullablePageReferenceSelectorWidgetFunction : CompositeWidgetFunctionBase + internal sealed class NullablePageReferenceSelectorWidgetFunction : PageReferenceSelectorWidgetFunctionBase { - public static string CompositeName - { - get - { - return _compositeNameBase + "OptionalPageSelector"; - } - } - - - private const string _compositeNameBase = CompositeWidgetFunctionBase.CommonNamespace + ".DataReference."; + public const string CompositeName = CompositeNameBase + ".OptionalPageSelector"; public NullablePageReferenceSelectorWidgetFunction(EntityTokenFactory entityTokenFactory) : base(CompositeName, typeof(NullableDataReference), entityTokenFactory) { } - - - public override XElement GetWidgetMarkup(ParameterList parameters, string label, HelpDefinition helpDefinition, string bindingSourceName) + protected override bool IsNullable() { - XElement selector = base.BuildBasicWidgetMarkup("DataReferenceTreeSelector", "Selected", label, helpDefinition, bindingSourceName); - - selector.Add( - new XAttribute("Handle", "Composite.Management.PageIdSelectorDialog"), - new XAttribute("DataType", TypeManager.SerializeType(typeof(IPage))), - new XAttribute("NullValueAllowed", true) - ); - - return selector; + return true; } - } + }; } diff --git a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunction.cs b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunction.cs index fb33e8632d..283eeecdda 100644 --- a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunction.cs +++ b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunction.cs @@ -1,45 +1,22 @@ -using System; -using System.Xml.Linq; using Composite.Data; -using Composite.Functions; using Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.Foundation; -using Composite.Core.Types; using Composite.Data.Types; namespace Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.DataReference { - internal sealed class PageReferenceSelectorWidgetFunction: CompositeWidgetFunctionBase + internal sealed class PageReferenceSelectorWidgetFunction : PageReferenceSelectorWidgetFunctionBase { - public static string CompositeName - { - get - { - return _compositeNameBase + "PageSelector"; - } - } - - - private const string _compositeNameBase = CompositeWidgetFunctionBase.CommonNamespace + ".DataReference."; + public const string CompositeName = CompositeNameBase + ".PageSelector"; public PageReferenceSelectorWidgetFunction(EntityTokenFactory entityTokenFactory) : base(CompositeName, typeof(DataReference), entityTokenFactory) { } - - - public override XElement GetWidgetMarkup(ParameterList parameters, string label, HelpDefinition helpDefinition, string bindingSourceName) + protected override bool IsNullable() { - XElement selector = base.BuildBasicWidgetMarkup("DataReferenceTreeSelector", "Selected", label, helpDefinition, bindingSourceName); - - selector.Add( - new XAttribute("Handle", "Composite.Management.PageIdSelectorDialog"), - new XAttribute("DataType", TypeManager.SerializeType(typeof(IPage))), - new XAttribute("NullValueAllowed", false) - ); - - return selector; + return false; } - } + }; } diff --git a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunctionBase.cs b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunctionBase.cs new file mode 100644 index 0000000000..609dc81993 --- /dev/null +++ b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunctionBase.cs @@ -0,0 +1,90 @@ +using System; +using System.Xml.Linq; +using Composite.C1Console.Elements; +using Composite.Core.Types; +using Composite.Core.Xml; +using Composite.Data.Types; +using Composite.Functions; +using Composite.Plugins.Elements.ElementProviders.PageElementProvider; +using Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.Foundation; + +namespace Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.DataReference +{ + internal abstract class PageReferenceSelectorWidgetFunctionBase : CompositeWidgetFunctionBase + { + protected const string CompositeNameBase = CommonNamespace + ".DataReference"; + + + private const string HomePageIdParameterName = "HomePageId"; + protected PageReferenceSelectorWidgetFunctionBase(string compositeName, Type returnType, EntityTokenFactory entityTokenFactory) : base(compositeName, returnType, entityTokenFactory) + { + base.AddParameterProfile( + new ParameterProfile(HomePageIdParameterName, typeof(Guid?), false, + new ConstantValueProvider(null), new WidgetFunctionProvider(new HomePageSelectorWidgetFunction(entityTokenFactory)), null, + "Filter by Home Page", new HelpDefinition("Use this field to filter by root website. If not set all websites are shown. You can use GetPageId function to get current page"))); + } + + public override XElement GetWidgetMarkup(ParameterList parameters, string label, HelpDefinition helpDefinition, string bindingSourceName) + { + var selector = BuildBasicWidgetMarkup("DataReferenceTreeSelector", "Selected", label, helpDefinition, bindingSourceName); + + selector.Add( + new XAttribute("Handle", "Composite.Management.PageIdSelectorDialog"), + new XAttribute("DataType", TypeManager.SerializeType(typeof(IPage))), + new XAttribute("NullValueAllowed", IsNullable()) + ); + var searchToken = GetSearchToken(parameters, selector); + if (searchToken != null) + selector.Add(searchToken); + + return selector; + } + + private XElement GetSearchToken(ParameterList parameters, XElement selector) + { + var parameter = GetParameterElement(parameters); + if (parameter == null) + return null; + + var f = Namespaces.BindingFormsStdFuncLib10; + var element = new XElement(selector.Name.Namespace + "DataReferenceTreeSelector.SearchToken", + new XElement(f + "StaticMethodCall", + new XAttribute("Type", TypeManager.SerializeType(typeof(PageReferenceSelectorWidgetFunctionBase))), + new XAttribute("Method", nameof(GetPageSearchToken)), + new XElement(f + "StaticMethodCall.Parameters", parameter))); + + return element; + } + + private object GetParameterElement(ParameterList parameters) + { + if (!parameters.TryGetParameterRuntimeTreeNode(HomePageIdParameterName, out var runtimeTreeNode)) + { + return null; + } + + if (runtimeTreeNode is FunctionParameterRuntimeTreeNode functionParamNode) + { + return functionParamNode.GetHostedFunction().Serialize(); + } + + if (runtimeTreeNode is ConstantParameterRuntimeTreeNode constParamNode) + { + return constParamNode.GetValue(); + } + + return null; + } + + public static string GetPageSearchToken(Guid? homePageId) + { + var token = new PageSearchToken + { + HomePageId = homePageId, + }; + return token.Serialize(); + } + + protected abstract bool IsNullable(); + }; +} \ No newline at end of file From e9728a55fcc1eab7806f270923b71709d6422c94 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 2 Jan 2020 17:21:13 +0100 Subject: [PATCH 12/38] CmsPageHttpHandler - removing duplicated tags from the head element --- Composite/AspNet/CmsPageHttpHandler.cs | 7 +- .../WebClient/Renderings/Page/PageRenderer.cs | 79 ++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/Composite/AspNet/CmsPageHttpHandler.cs b/Composite/AspNet/CmsPageHttpHandler.cs index 79caeb53fa..f530dc0ef0 100644 --- a/Composite/AspNet/CmsPageHttpHandler.cs +++ b/Composite/AspNet/CmsPageHttpHandler.cs @@ -1,4 +1,4 @@ -using System.Web; +using System.Web; using System.Xml.Linq; using Composite.AspNet.Caching; using Composite.Core.Configuration; @@ -11,7 +11,7 @@ namespace Composite.AspNet { /// - /// Renders page tempates without building a Web Form's control tree. + /// Renders page templates without building a Web Form's control tree. /// Contains a custom implementation of "donut caching". /// internal class CmsPageHttpHandler: IHttpHandler @@ -98,6 +98,7 @@ public void ProcessRequest(HttpContext context) var xhtmlDocument = new XhtmlDocument(document); PageRenderer.ProcessXhtmlDocument(xhtmlDocument, renderingContext.Page); + PageRenderer.ProcessDocumentHead(xhtmlDocument); xhtml = xhtmlDocument.ToString(); } @@ -125,7 +126,7 @@ public void ProcessRequest(HttpContext context) context.Response.Cache.SetNoServerCaching(); } - // Inserting perfomance profiling information + // Inserting performance profiling information if (renderingContext.ProfilingEnabled) { xhtml = renderingContext.BuildProfilerReport(); diff --git a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs index c8ac8407e1..f00a41c943 100644 --- a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs +++ b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.HtmlControls; @@ -31,6 +32,8 @@ public static class PageRenderer private static readonly NameBasedAttributeComparer _nameBasedAttributeComparer = new NameBasedAttributeComparer(); private static readonly XName XName_function = Namespaces.Function10 + "function"; + private static readonly XName XName_Id = "id"; + private static readonly XName XName_Name = "name"; /// public static FunctionContextContainer GetPageRenderFunctionContextContainer() @@ -55,7 +58,7 @@ public static Control Render(this IPage page, IEnumerable e.Name.LocalName.Equals("meta", StringComparison.OrdinalIgnoreCase); + + private static bool CheckForDuplication(HashSet values, string value) + { + if (!string.IsNullOrWhiteSpace(value)) + { + if (values.Contains(value)) return true; + + values.Add(value); + } + + return false; + } + + private static string AttributesAsString(this XElement e) + { + var str = new StringBuilder(); + foreach (var attr in e.Attributes().OrderBy(a => a.Name.NamespaceName).ThenBy(a => a.Name.LocalName)) + { + str.Append(attr.Name.LocalName); + str.Append("=\""); + str.Append(attr.Value); + str.Append("\" "); + } + + return str.ToString(); + } + + /// + public static void ProcessDocumentHead(XhtmlDocument xhtmlDocument) + { + var head = xhtmlDocument.Head; + + var uniqueIdValues = new HashSet(StringComparer.OrdinalIgnoreCase); + var uniqueMetaNameValues = new HashSet(StringComparer.OrdinalIgnoreCase); + var uniqueScriptAttributes = new HashSet(StringComparer.OrdinalIgnoreCase); + var uniqueLinkAttributes = new HashSet(StringComparer.OrdinalIgnoreCase); + + var priorityOrderedElements = new List(); + + priorityOrderedElements.AddRange(head.Elements().Where(IsMetaTag)); + priorityOrderedElements.Reverse(); + priorityOrderedElements.AddRange(head.Elements().Where(e => !IsMetaTag(e))); + + foreach (var e in priorityOrderedElements) + { + var id = (string) e.Attribute(XName_Id); + + bool toBeRemoved = CheckForDuplication(uniqueIdValues, id); + + if (!toBeRemoved && !e.Nodes().Any()) + { + switch (e.Name.LocalName.ToLowerInvariant()) + { + case "meta": + var name = (string) e.Attribute(XName_Name); + toBeRemoved = CheckForDuplication(uniqueMetaNameValues, name); + break; + case "script": + toBeRemoved = CheckForDuplication(uniqueScriptAttributes, e.AttributesAsString()); + break; + case "link": + toBeRemoved = CheckForDuplication(uniqueLinkAttributes, e.AttributesAsString()); + break; + } + } + + if (toBeRemoved) + { + e.Remove(); + } + } + } + /// public static Control Render(XDocument document, FunctionContextContainer contextContainer, IXElementToControlMapper mapper, IPage page) From a7714c06b6a5706994411e7199dedc89813b51a9 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 3 Jan 2020 14:43:06 +0100 Subject: [PATCH 13/38] Adding a configuration setting 'omitAspNetWebFormsSupport', which switches on the usage of the CmsPageHttpHandler --- .../BuildinGlobalSettingsProvider.cs | 2 ++ .../GlobalSettingsProviderPluginFacade.cs | 3 ++- .../Configuration/GlobalSettingsFacade.cs | 11 ++++++++- .../Configuration/GlobalSettingsFacadeImpl.cs | 4 +++- .../Configuration/IGlobalSettingsFacade.cs | 3 ++- .../IGlobalSettingsProvider.cs | 4 +++- .../Core/Routing/Pages/C1PageRouteHander.cs | 23 +++++++++++-------- .../WebClient/Renderings/Page/PageRenderer.cs | 17 ++++++++------ .../ConfigBasedGlobalSettingsProvider.cs | 13 +++++++++-- .../Composite/DebugBuild.Composite.config | 1 + .../Composite/ReleaseBuild.Composite.config | 3 ++- 11 files changed, 59 insertions(+), 25 deletions(-) diff --git a/Composite/Core/Configuration/BuildinPlugins/GlobalSettingsProvider/BuildinGlobalSettingsProvider.cs b/Composite/Core/Configuration/BuildinPlugins/GlobalSettingsProvider/BuildinGlobalSettingsProvider.cs index 5f6238a023..3207ada8ef 100644 --- a/Composite/Core/Configuration/BuildinPlugins/GlobalSettingsProvider/BuildinGlobalSettingsProvider.cs +++ b/Composite/Core/Configuration/BuildinPlugins/GlobalSettingsProvider/BuildinGlobalSettingsProvider.cs @@ -139,5 +139,7 @@ public string SerializedWorkflowsDirectory public TimeZoneInfo TimeZone => _timezone; public bool InheritGlobalReadPermissionOnHiddenPerspectives => false; + + public bool OmitAspNetWebFormsSupport => false; } } diff --git a/Composite/Core/Configuration/Foundation/PluginFacades/GlobalSettingsProviderPluginFacade.cs b/Composite/Core/Configuration/Foundation/PluginFacades/GlobalSettingsProviderPluginFacade.cs index 2bd6a1985b..26f5d0fe2c 100644 --- a/Composite/Core/Configuration/Foundation/PluginFacades/GlobalSettingsProviderPluginFacade.cs +++ b/Composite/Core/Configuration/Foundation/PluginFacades/GlobalSettingsProviderPluginFacade.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Configuration; using Composite.Core.Collections.Generic; @@ -326,6 +326,7 @@ public static bool PrettifyRenderFunctionExceptions public static bool InheritGlobalReadPermissionOnHiddenPerspectives => UseReaderLock(p => p.InheritGlobalReadPermissionOnHiddenPerspectives); + public static bool OmitAspNetWebFormsSupport => UseReaderLock(p => p.OmitAspNetWebFormsSupport); private static void Flush() { diff --git a/Composite/Core/Configuration/GlobalSettingsFacade.cs b/Composite/Core/Configuration/GlobalSettingsFacade.cs index 5ed1639cb6..969c30ab72 100644 --- a/Composite/Core/Configuration/GlobalSettingsFacade.cs +++ b/Composite/Core/Configuration/GlobalSettingsFacade.cs @@ -1,7 +1,9 @@ -using System; +using System; using System.Linq; using System.Collections.Generic; using System.Globalization; +using Composite.Core.PageTemplates; +using Composite.Plugins.PageTemplates.Razor; namespace Composite.Core.Configuration @@ -248,6 +250,13 @@ public static void RemoveNonProbableAssemblyName(string assemblyNamePatern) public static bool InheritGlobalReadPermissionOnHiddenPerspectives => _globalSettingsFacade.InheritGlobalReadPermissionOnHiddenPerspectives; + /// + /// When true, a page request handler that doesn't support UserControl functions will be used. + /// Applicable for -s, that return renderer-s implementing interface + /// (f.e. ). + /// + public static bool OmitAspNetWebFormsSupport => _globalSettingsFacade.OmitAspNetWebFormsSupport; + // Overload /// public static CachingSettings GetNamedCaching(string name) diff --git a/Composite/Core/Configuration/GlobalSettingsFacadeImpl.cs b/Composite/Core/Configuration/GlobalSettingsFacadeImpl.cs index f5f07a068b..f2e7884204 100644 --- a/Composite/Core/Configuration/GlobalSettingsFacadeImpl.cs +++ b/Composite/Core/Configuration/GlobalSettingsFacadeImpl.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using Composite.Core.Configuration.Foundation.PluginFacades; @@ -166,5 +166,7 @@ public void RemoveNonProbableAssemblyName(string assemblyNamePatern) public bool InheritGlobalReadPermissionOnHiddenPerspectives => GlobalSettingsProviderPluginFacade.InheritGlobalReadPermissionOnHiddenPerspectives; + + public bool OmitAspNetWebFormsSupport => GlobalSettingsProviderPluginFacade.OmitAspNetWebFormsSupport; } } \ No newline at end of file diff --git a/Composite/Core/Configuration/IGlobalSettingsFacade.cs b/Composite/Core/Configuration/IGlobalSettingsFacade.cs index ee3495bb83..ecaa4a690c 100644 --- a/Composite/Core/Configuration/IGlobalSettingsFacade.cs +++ b/Composite/Core/Configuration/IGlobalSettingsFacade.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; @@ -44,5 +44,6 @@ internal interface IGlobalSettingsFacade bool FunctionPreviewEnabled { get; } TimeZoneInfo TimeZone { get; } bool InheritGlobalReadPermissionOnHiddenPerspectives { get; } + bool OmitAspNetWebFormsSupport { get; } } } diff --git a/Composite/Core/Configuration/Plugins/GlobalSettingsProvider/IGlobalSettingsProvider.cs b/Composite/Core/Configuration/Plugins/GlobalSettingsProvider/IGlobalSettingsProvider.cs index 1540c192b3..6482b8b219 100644 --- a/Composite/Core/Configuration/Plugins/GlobalSettingsProvider/IGlobalSettingsProvider.cs +++ b/Composite/Core/Configuration/Plugins/GlobalSettingsProvider/IGlobalSettingsProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Composite.Core.Configuration.Plugins.GlobalSettingsProvider.Runtime; using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder; @@ -87,5 +87,7 @@ internal interface IGlobalSettingsProvider TimeZoneInfo TimeZone { get; } bool InheritGlobalReadPermissionOnHiddenPerspectives { get; } + + bool OmitAspNetWebFormsSupport { get; } } } diff --git a/Composite/Core/Routing/Pages/C1PageRouteHander.cs b/Composite/Core/Routing/Pages/C1PageRouteHander.cs index 2cf13d13af..cc119c6d49 100644 --- a/Composite/Core/Routing/Pages/C1PageRouteHander.cs +++ b/Composite/Core/Routing/Pages/C1PageRouteHander.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; using System.Collections.Concurrent; using System.Linq; @@ -10,6 +10,7 @@ using System.Web.UI; using System.Xml.Linq; using Composite.AspNet; +using Composite.Core.Configuration; using Composite.Core.Extensions; using Composite.Core.Linq; using Composite.Core.PageTemplates; @@ -19,6 +20,9 @@ namespace Composite.Core.Routing.Pages { internal class C1PageRouteHandler : IRouteHandler { + private const string PageHandlerPath = "Renderers/Page.aspx"; + private const string PageHandlerVirtualPath = "~/" + PageHandlerPath; + private readonly PageUrlData _pageUrlData; private static readonly Type _handlerType; @@ -43,9 +47,8 @@ static C1PageRouteHandler() var handler = handlers .Elements("add") - .Where(e => e.Attribute("path") != null - && e.Attribute("path").Value.Equals("Renderers/Page.aspx", StringComparison.OrdinalIgnoreCase)) - .SingleOrDefaultOrException("Multiple handlers for 'Renderers/Page.aspx' were found'"); + .Where(e => e.Attribute("path")?.Value.Equals(PageHandlerPath, StringComparison.OrdinalIgnoreCase) ?? false) + .SingleOrDefaultOrException($"Multiple handlers for '{PageHandlerPath}' were found'"); if (handler != null) { @@ -94,17 +97,17 @@ public IHttpHandler GetHttpHandler(RequestContext requestContext) context.Response.Cache.SetCacheability(HttpCacheability.NoCache); } - if (IsSlimPageRenderer(_pageUrlData.GetPage().TemplateId)) + if (_handlerType != null) { - return new CmsPageHttpHandler(); + return (IHttpHandler)Activator.CreateInstance(_handlerType); } - if (_handlerType != null) + if (GlobalSettingsFacade.OmitAspNetWebFormsSupport && IsSlimPageRenderer(_pageUrlData.GetPage().TemplateId)) { - return (IHttpHandler)Activator.CreateInstance(_handlerType); + return new CmsPageHttpHandler(); } - - return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath("~/Renderers/Page.aspx", typeof(Page)); + + return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(PageHandlerVirtualPath, typeof(Page)); } private static readonly ConcurrentDictionary _pageRendererTypCache diff --git a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs index f00a41c943..c4868ffc76 100644 --- a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs +++ b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs @@ -252,14 +252,14 @@ internal static void ProcessXhtmlDocument(XhtmlDocument xhtmlDocument, IPage pag ResolveRelativePaths(xhtmlDocument); } - using (Profiler.Measure("Sorting elements")) + using (Profiler.Measure("Appending C1 meta tags")) { - PrioritizeHeadNodes(xhtmlDocument); + AppendC1MetaTags(page, xhtmlDocument); } - using (Profiler.Measure("Appending C1 meta tags")) + using (Profiler.Measure("Sorting elements")) { - AppendC1MetaTags(page, xhtmlDocument); + PrioritizeHeadNodes(xhtmlDocument); } using (Profiler.Measure("Parsing localization strings")) @@ -313,8 +313,11 @@ private static string AttributesAsString(this XElement e) /// public static void ProcessDocumentHead(XhtmlDocument xhtmlDocument) { - var head = xhtmlDocument.Head; + RemoveDuplicates(xhtmlDocument.Head); + } + private static void RemoveDuplicates(XElement head) + { var uniqueIdValues = new HashSet(StringComparer.OrdinalIgnoreCase); var uniqueMetaNameValues = new HashSet(StringComparer.OrdinalIgnoreCase); var uniqueScriptAttributes = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -328,7 +331,7 @@ public static void ProcessDocumentHead(XhtmlDocument xhtmlDocument) foreach (var e in priorityOrderedElements) { - var id = (string) e.Attribute(XName_Id); + var id = (string)e.Attribute(XName_Id); bool toBeRemoved = CheckForDuplication(uniqueIdValues, id); @@ -337,7 +340,7 @@ public static void ProcessDocumentHead(XhtmlDocument xhtmlDocument) switch (e.Name.LocalName.ToLowerInvariant()) { case "meta": - var name = (string) e.Attribute(XName_Name); + var name = (string)e.Attribute(XName_Name); toBeRemoved = CheckForDuplication(uniqueMetaNameValues, name); break; case "script": diff --git a/Composite/Plugins/GlobalSettings/GlobalSettingsProviders/ConfigBasedGlobalSettingsProvider.cs b/Composite/Plugins/GlobalSettings/GlobalSettingsProviders/ConfigBasedGlobalSettingsProvider.cs index 8d5c9ac0f3..5a2789d55f 100644 --- a/Composite/Plugins/GlobalSettings/GlobalSettingsProviders/ConfigBasedGlobalSettingsProvider.cs +++ b/Composite/Plugins/GlobalSettings/GlobalSettingsProviders/ConfigBasedGlobalSettingsProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Configuration; using System.Linq; @@ -101,6 +101,8 @@ public ConfigBasedGlobalSettingsProvider(ConfigBasedGlobalSettingsProviderData c public bool InheritGlobalReadPermissionOnHiddenPerspectives => _configurationData.InheritGlobalReadPermissionOnHiddenPerspectives; + + public bool OmitAspNetWebFormsSupport => _configurationData.OmitAspNetWebFormsSupport; } internal class ConfigCachingSettings: ICachingSettings @@ -520,7 +522,14 @@ public bool InheritGlobalReadPermissionOnHiddenPerspectives get { return (bool)base[InheritGlobalReadPermissionOnHiddenPerspectivesPropertyName]; } set { base[InheritGlobalReadPermissionOnHiddenPerspectivesPropertyName] = value; } } - + + private const string _omitAspNetWebFormsSupportPropertyName = "omitAspNetWebFormsSupport"; + [ConfigurationProperty(_omitAspNetWebFormsSupportPropertyName, DefaultValue = false)] + public bool OmitAspNetWebFormsSupport + { + get { return (bool)base[_omitAspNetWebFormsSupportPropertyName]; } + set { base[_omitAspNetWebFormsSupportPropertyName] = value; } + } } diff --git a/Website/App_Data/Composite/DebugBuild.Composite.config b/Website/App_Data/Composite/DebugBuild.Composite.config index c1c5c02345..58dd780f4a 100644 --- a/Website/App_Data/Composite/DebugBuild.Composite.config +++ b/Website/App_Data/Composite/DebugBuild.Composite.config @@ -98,6 +98,7 @@ prettifyRenderFunctionExceptions="true" functionPreviewEnabled="true" inheritGlobalReadPermissionOnHiddenPerspectives="false" + omitAspNetWebFormsSupport="false" > diff --git a/Website/App_Data/Composite/ReleaseBuild.Composite.config b/Website/App_Data/Composite/ReleaseBuild.Composite.config index b257af27c6..7b8ff8b4b9 100644 --- a/Website/App_Data/Composite/ReleaseBuild.Composite.config +++ b/Website/App_Data/Composite/ReleaseBuild.Composite.config @@ -91,8 +91,9 @@ imageQuality="80" prettifyPublicMarkup="true" prettifyRenderFunctionExceptions="true" - functionPreviewEnabled="true" + functionPreviewEnabled="true" inheritGlobalReadPermissionOnHiddenPerspectives="false" + omitAspNetWebFormsSupport="false" > From d41c5d38f4bbe55f602a6209cfca0ae1f6c5a0d6 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 6 Jan 2020 11:10:42 +0100 Subject: [PATCH 14/38] Fixing typos, refactoring, fixing appearing before in html . --- Composite/AspNet/CmsPageHttpHandler.cs | 2 +- .../PageTemplates/TemplateDefinitionHelper.cs | 10 +++---- .../WebClient/Renderings/Page/PageRenderer.cs | 12 ++++---- .../PluginFacades/FunctionWrapper.cs | 12 ++------ .../Functions/FunctionRuntimeTreeNode.cs | 29 +++++++++++-------- 5 files changed, 31 insertions(+), 34 deletions(-) diff --git a/Composite/AspNet/CmsPageHttpHandler.cs b/Composite/AspNet/CmsPageHttpHandler.cs index f530dc0ef0..667f0f6506 100644 --- a/Composite/AspNet/CmsPageHttpHandler.cs +++ b/Composite/AspNet/CmsPageHttpHandler.cs @@ -66,7 +66,7 @@ public void ProcessRequest(HttpContext context) document = slimRenderer.Render(renderingContext.PageContentToRender, functionContext); } - allFunctionsExecuted = PageRenderer.ExecuteCachebleFuctions(document.Root, functionContext); + allFunctionsExecuted = PageRenderer.ExecuteCacheableFunctions(document.Root, functionContext); if (cachingEnabled && !allFunctionsExecuted && OutputCacheHelper.ResponseCachebale(context)) { diff --git a/Composite/Core/PageTemplates/TemplateDefinitionHelper.cs b/Composite/Core/PageTemplates/TemplateDefinitionHelper.cs index f763bd11f6..e391f5a2d1 100644 --- a/Composite/Core/PageTemplates/TemplateDefinitionHelper.cs +++ b/Composite/Core/PageTemplates/TemplateDefinitionHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -58,7 +58,7 @@ public static DescriptorType BuildPageTemplateDescriptor(IPageTe var placeholderAttributes = property.GetCustomAttributes(typeof(PlaceholderAttribute), true); if (placeholderAttributes.Length == 0) continue; - Verify.That(placeholderAttributes.Length == 1, "Multiple '{0}' attributes defined on property", typeof(PlaceholderAttribute), property.Name); + Verify.That(placeholderAttributes.Length == 1, $"Multiple '{typeof(PlaceholderAttribute)}' attributes defined on property '{property.Name}'"); var placeholderAttribute = (PlaceholderAttribute)placeholderAttributes[0]; @@ -125,11 +125,11 @@ public static void BindPlaceholders(IPageTemplate template, if (functionContextContainer != null) { - bool allFunctionsExecuted = false; + bool allFunctionsExecuted; using (Profiler.Measure($"Evaluating placeholder '{placeholderId}'")) { - allFunctionsExecuted = PageRenderer.ExecuteCachebleFuctions(placeholderXhtml.Root, functionContextContainer); + allFunctionsExecuted = PageRenderer.ExecuteCacheableFunctions(placeholderXhtml.Root, functionContextContainer); } if (allFunctionsExecuted) @@ -152,7 +152,7 @@ public static void BindPlaceholders(IPageTemplate template, Verify.IsNotNull(property, "Failed to find placeholder property '{0}'", propertyName); } - property.SetValue(template, placeholderXhtml, new object[0]); + property.SetValue(template, placeholderXhtml); } } } diff --git a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs index c4868ffc76..12f3270ced 100644 --- a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs +++ b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs @@ -430,7 +430,7 @@ private static int GetHeadNodePriority(XNode headNode) if (headElement.Attribute("name") != null) return 20; - if (headElement.Attribute("property") != null) return 30; + if (headElement.Attribute("property") != null) return 25; return 20; } @@ -670,18 +670,18 @@ public static void ExecuteEmbeddedFunctions(XElement element, FunctionContextCon } /// - /// Executes all cacheble (not dynamic) functions and returs True - /// if all of the functions were cacheble. + /// Executes all cacheable (not dynamic) functions and returns True + /// if all of the functions were cacheable. /// /// /// /// - internal static bool ExecuteCachebleFuctions(XElement element, FunctionContextContainer functionContext) + internal static bool ExecuteCacheableFunctions(XElement element, FunctionContextContainer functionContext) { return ExecuteFunctionsRec(element, functionContext, name => { - var function = FunctionFacade.GetFunction(name) as IDynamicFunction; - return function == null || !function.PreventFunctionOutputCaching; + var function = FunctionFacade.GetFunction(name); + return !(function is IDynamicFunction df && df.PreventFunctionOutputCaching); }); } diff --git a/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs b/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs index 9c9b3999d8..7d536e0323 100644 --- a/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs +++ b/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Web; @@ -157,14 +157,6 @@ bool IFunctionInitializationInfo.FunctionInitializedCorrectly } } - public bool PreventFunctionOutputCaching - { - get - { - var dynamicFunction = _functionToWrap as IDynamicFunction; - return dynamicFunction != null && dynamicFunction.PreventFunctionOutputCaching; - } - } - + public bool PreventFunctionOutputCaching => _functionToWrap is IDynamicFunction df && df.PreventFunctionOutputCaching; } } diff --git a/Composite/Functions/FunctionRuntimeTreeNode.cs b/Composite/Functions/FunctionRuntimeTreeNode.cs index d7c6d98b3e..2a121694e5 100644 --- a/Composite/Functions/FunctionRuntimeTreeNode.cs +++ b/Composite/Functions/FunctionRuntimeTreeNode.cs @@ -49,14 +49,16 @@ internal FunctionRuntimeTreeNode(IFunction function, List public override object GetValue(FunctionContextContainer contextContainer) { - using (TimerProfilerFacade.CreateTimerProfiler(this.GetNamespace() + "." + this.GetName())) - { - if (contextContainer == null) throw new ArgumentNullException("contextContainer"); + if (contextContainer == null) throw new ArgumentNullException("contextContainer"); + + string functionName = _function.CompositeName() ?? ""; + using (TimerProfilerFacade.CreateTimerProfiler(functionName)) + { ValidateNotSelfCalling(); try - { + { var parameters = new ParameterList(contextContainer); foreach (ParameterProfile parameterProfile in _function.ParameterProfiles) @@ -98,7 +100,7 @@ public override object GetValue(FunctionContextContainer contextContainer) } catch (Exception ex) { - throw new InvalidOperationException(string.Format("Failed to get value for parameter '{0}' in function '{1}'.", parameterProfile.Name, _function.CompositeName()), ex); + throw new InvalidOperationException($"Failed to get value for parameter '{parameterProfile.Name}' in function '{functionName}'.", ex); } parameters.AddConstantParameter(parameterProfile.Name, value, parameterProfile.Type, true); } @@ -108,20 +110,23 @@ public override object GetValue(FunctionContextContainer contextContainer) IDisposable measurement = null; try { - string functionName = _function.CompositeName(); if (functionName != "Composite.Utils.GetInputParameter") { - measurement = Profiler.Measure(functionName ?? "", () => _function.EntityToken); + var nodeToLog = functionName; + + if (_function is IDynamicFunction df && df.PreventFunctionOutputCaching) + { + nodeToLog += " (PreventCaching)"; + } + + measurement = Profiler.Measure(nodeToLog, () => _function.EntityToken); } result = _function.Execute(parameters, contextContainer); } finally { - if (measurement != null) - { - measurement.Dispose(); - } + measurement?.Dispose(); } return result; @@ -132,7 +137,7 @@ public override object GetValue(FunctionContextContainer contextContainer) } catch (Exception ex) { - throw new InvalidOperationException("Failed to get value for function '{0}'".FormatWith(_function.CompositeName()), ex); + throw new InvalidOperationException($"Failed to get value for function '{functionName}'", ex); } } } From da430141727bbdb0a6dd633ab74432235e65787b Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 6 Jan 2020 16:10:18 +0100 Subject: [PATCH 15/38] Preventing donut caching for pages with suppressed exceptions shown --- Composite/AspNet/CmsPageHttpHandler.cs | 9 ++++++--- Composite/Functions/FunctionContextContainer.cs | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Composite/AspNet/CmsPageHttpHandler.cs b/Composite/AspNet/CmsPageHttpHandler.cs index 667f0f6506..4ba35b8673 100644 --- a/Composite/AspNet/CmsPageHttpHandler.cs +++ b/Composite/AspNet/CmsPageHttpHandler.cs @@ -65,16 +65,19 @@ public void ProcessRequest(HttpContext context) { document = slimRenderer.Render(renderingContext.PageContentToRender, functionContext); } - + allFunctionsExecuted = PageRenderer.ExecuteCacheableFunctions(document.Root, functionContext); if (cachingEnabled && !allFunctionsExecuted && OutputCacheHelper.ResponseCachebale(context)) { preventResponseCaching = true; - using (Profiler.Measure("Adding to cache")) + if (!functionContext.ExceptionsSuppressed) { - OutputCacheHelper.AddToCache(context, cacheKey, new DonutCacheEntry(context, document)); + using (Profiler.Measure("Adding to cache")) + { + OutputCacheHelper.AddToCache(context, cacheKey, new DonutCacheEntry(context, document)); + } } } } diff --git a/Composite/Functions/FunctionContextContainer.cs b/Composite/Functions/FunctionContextContainer.cs index c8838e0e05..ff0ec83ad1 100644 --- a/Composite/Functions/FunctionContextContainer.cs +++ b/Composite/Functions/FunctionContextContainer.cs @@ -23,6 +23,8 @@ public sealed class FunctionContextContainer private readonly ParameterList _parameterList; private readonly Dictionary _parameterDictionary; + internal bool ExceptionsSuppressed { get; private set; } + #region constructors /// public FunctionContextContainer() @@ -59,7 +61,7 @@ public FunctionContextContainer(FunctionContextContainer inheritFromContainer, D /// - /// Used for embeding ASP.NET controls into xhtml markup. + /// Used for embedding ASP.NET controls into xhtml markup. /// public IFunctionResultToXEmbedableMapper XEmbedableMapper { get; set; } @@ -143,6 +145,7 @@ public bool ProcessException(string functionName, Exception exception, string lo Log.LogError("Function: " + functionName, exception); errorBoxHtml = XhtmlErrorFormatter.GetErrorDescriptionHtmlElement(exception, functionName); + ExceptionsSuppressed = true; return true; } From 86bda59951e5ef3d99e01fe7fca27108b27b60c9 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 6 Jan 2020 16:35:36 +0100 Subject: [PATCH 16/38] 404 for internal links pointing to not existing pages; PageRederer refactoring/cleanup --- .../Core/Routing/Pages/C1PageRouteHander.cs | 10 ++++- .../WebClient/Renderings/Page/PageRenderer.cs | 44 ++----------------- 2 files changed, 11 insertions(+), 43 deletions(-) diff --git a/Composite/Core/Routing/Pages/C1PageRouteHander.cs b/Composite/Core/Routing/Pages/C1PageRouteHander.cs index cc119c6d49..3d52c0f17f 100644 --- a/Composite/Core/Routing/Pages/C1PageRouteHander.cs +++ b/Composite/Core/Routing/Pages/C1PageRouteHander.cs @@ -102,9 +102,15 @@ public IHttpHandler GetHttpHandler(RequestContext requestContext) return (IHttpHandler)Activator.CreateInstance(_handlerType); } - if (GlobalSettingsFacade.OmitAspNetWebFormsSupport && IsSlimPageRenderer(_pageUrlData.GetPage().TemplateId)) + if (GlobalSettingsFacade.OmitAspNetWebFormsSupport) { - return new CmsPageHttpHandler(); + var page = _pageUrlData.GetPage() + ?? throw new HttpException(404, "Page not found - either this page has not been published yet or it has been deleted."); + + if (IsSlimPageRenderer(page.TemplateId)) + { + return new CmsPageHttpHandler(); + } } return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(PageHandlerVirtualPath, typeof(Page)); diff --git a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs index 12f3270ced..cb4814b823 100644 --- a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs +++ b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs @@ -125,15 +125,6 @@ private static void ResolvePlaceholders(XDocument document, IEnumerable - public static Guid CurrentPageId - { - get - { - if (!RequestLifetimeCache.HasKey("PageRenderer.IPage")) - { - return Guid.Empty; - } - - return RequestLifetimeCache.TryGet("PageRenderer.IPage").Id; - } - } + public static Guid CurrentPageId => CurrentPage?.Id ?? Guid.Empty; /// @@ -183,11 +163,6 @@ public static IPage CurrentPage { get { - if (!RequestLifetimeCache.HasKey("PageRenderer.IPage")) - { - return null; - } - return RequestLifetimeCache.TryGet("PageRenderer.IPage"); } set @@ -207,20 +182,7 @@ public static IPage CurrentPage /// - public static CultureInfo CurrentPageCulture - { - get - { - if (!RequestLifetimeCache.HasKey("PageRenderer.IPage")) - { - return null; - } - - var page = RequestLifetimeCache.TryGet("PageRenderer.IPage"); - return page.DataSourceId.LocaleScope; - } - } - + public static CultureInfo CurrentPageCulture => CurrentPage?.DataSourceId.LocaleScope; /// @@ -645,7 +607,7 @@ internal static bool ExecuteFunctionsRec( } catch (Exception ex) { - using (Profiler.Measure("PageRenderer. Logging an exception")) + using (Profiler.Measure("PageRenderer. Logging exception: " + ex.Message)) { XElement errorBoxHtml; From 19841c67724f325e929a3ce2e521b18487abb5b8 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 8 Jan 2020 11:54:08 +0100 Subject: [PATCH 17/38] Implementing ISlimPageRenderer for XmlPageRenderer --- Composite/Core/PageTemplates/IPageRenderer.cs | 10 +++++----- .../WebClient/Renderings/Page/PageRenderer.cs | 9 ++++++++- .../PageTemplates/Razor/RazorPageRenderer.cs | 6 +++--- .../XmlPageTemplates/XmlPageRenderer.cs | 20 +++++++++++++++---- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/Composite/Core/PageTemplates/IPageRenderer.cs b/Composite/Core/PageTemplates/IPageRenderer.cs index eeebe6fc03..c914c7abb0 100644 --- a/Composite/Core/PageTemplates/IPageRenderer.cs +++ b/Composite/Core/PageTemplates/IPageRenderer.cs @@ -1,20 +1,20 @@ -using System.Xml.Linq; +using System.Xml.Linq; using Composite.Functions; namespace Composite.Core.PageTemplates { /// - /// This class is responsible for rendering the provided job onto the provided asp.net web forms page. + /// This class is responsible for rendering the provided job onto the provided ASP.NET Web Forms page. /// The AttachToPage method is called at page construction and is expected to hook on to asp.net page events (like PreInit) to drive the rendering. /// public interface IPageRenderer { /// - /// Attaches rendering code to an instace of . + /// Attaches rendering code to an instance of . /// - /// The render taget. + /// The render target. /// The render job. - void AttachToPage(System.Web.UI.Page renderTaget, PageContentToRender contentToRender); + void AttachToPage(System.Web.UI.Page renderTarget, PageContentToRender contentToRender); } /// diff --git a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs index cb4814b823..ec81ff8ae3 100644 --- a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs +++ b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs @@ -19,6 +19,7 @@ using Composite.Core.Xml; using Composite.C1Console.Security; using Composite.Core.Configuration; +using Composite.Plugins.PageTemplates.XmlPageTemplates; namespace Composite.Core.WebClient.Renderings.Page { @@ -105,7 +106,13 @@ public static XhtmlDocument ParsePlaceholderContent(IPagePlaceholderContent plac return XhtmlDocument.Parse($"{placeholderContent.Content}"); } - private static void ResolvePlaceholders(XDocument document, IEnumerable placeholderContents) + + /// + /// Replaces <rendering:placeholder ... /> tags with provided placeholder contents. Used by . + /// + /// The document to be updated. + /// The placeholder content to be used. + internal static void ResolvePlaceholders(XDocument document, IEnumerable placeholderContents) { using (TimerProfilerFacade.CreateTimerProfiler()) { diff --git a/Composite/Plugins/PageTemplates/Razor/RazorPageRenderer.cs b/Composite/Plugins/PageTemplates/Razor/RazorPageRenderer.cs index 25476cacfa..ba464dc7d9 100644 --- a/Composite/Plugins/PageTemplates/Razor/RazorPageRenderer.cs +++ b/Composite/Plugins/PageTemplates/Razor/RazorPageRenderer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Text; using System.Web; @@ -30,9 +30,9 @@ public RazorPageRenderer( private Page _aspnetPage; private PageContentToRender _job; - public void AttachToPage(Page renderTaget, PageContentToRender contentToRender) + public void AttachToPage(Page renderTarget, PageContentToRender contentToRender) { - _aspnetPage = renderTaget; + _aspnetPage = renderTarget; _job = contentToRender; _aspnetPage.Init += RendererPage; diff --git a/Composite/Plugins/PageTemplates/XmlPageTemplates/XmlPageRenderer.cs b/Composite/Plugins/PageTemplates/XmlPageTemplates/XmlPageRenderer.cs index 8e4181c00d..1b0757b394 100644 --- a/Composite/Plugins/PageTemplates/XmlPageTemplates/XmlPageRenderer.cs +++ b/Composite/Plugins/PageTemplates/XmlPageTemplates/XmlPageRenderer.cs @@ -1,24 +1,36 @@ -using System; +using System; using System.Web.UI; +using System.Xml.Linq; using Composite.Core.PageTemplates; using Composite.Core.Instrumentation; using Composite.Core.WebClient.Renderings.Page; +using Composite.Core.WebClient.Renderings.Template; +using Composite.Functions; namespace Composite.Plugins.PageTemplates.XmlPageTemplates { - internal class XmlPageRenderer: IPageRenderer + internal class XmlPageRenderer: IPageRenderer, ISlimPageRenderer { private Page _aspnetPage; private PageContentToRender _job; - public void AttachToPage(Page renderTaget, PageContentToRender contentToRender) + public void AttachToPage(Page renderTarget, PageContentToRender contentToRender) { - _aspnetPage = renderTaget; + _aspnetPage = renderTarget; _job = contentToRender; _aspnetPage.Init += RendererPage; } + public XDocument Render(PageContentToRender contentToRender, FunctionContextContainer functionContextContainer) + { + var document = TemplateInfo.GetTemplateDocument(contentToRender.Page.TemplateId); + + PageRenderer.ResolvePlaceholders(document, contentToRender.Contents); + + return document; + } + private void RendererPage(object sender, EventArgs e) { if (_aspnetPage.Master != null) From 78b472aa8ca012fb2eaa38c2f9863cf8c0ed2794 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 8 Jan 2020 16:02:15 +0100 Subject: [PATCH 18/38] Making C1PageRoute/CmsPageHttpRequest handle page preview requests --- Composite/Composite.csproj | 5 +- Composite/Core/Routing/Pages/C1PageRoute.cs | 24 +++++-- .../Renderings/Page/PagePreviewBuilder.cs | 18 ++--- .../Renderings/Page/PagePreviewContext.cs | 69 +++++++++++++++++++ .../WebClient/Renderings/RenderingContext.cs | 19 ++--- 5 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 Composite/Core/WebClient/Renderings/Page/PagePreviewContext.cs diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 5fb3af357b..d0bd2ca32c 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -256,6 +256,7 @@ + @@ -263,7 +264,7 @@ - + @@ -2694,7 +2695,7 @@ --> - + $(ProjectDir)git_branch.txt $(ProjectDir)git_commithash.txt diff --git a/Composite/Core/Routing/Pages/C1PageRoute.cs b/Composite/Core/Routing/Pages/C1PageRoute.cs index fbaf87025e..cab1a4b8ec 100644 --- a/Composite/Core/Routing/Pages/C1PageRoute.cs +++ b/Composite/Core/Routing/Pages/C1PageRoute.cs @@ -1,10 +1,13 @@ -using System; +using System; using System.Globalization; using System.Web; using System.Web.Routing; using Composite.Core.WebClient; using Composite.Core.Configuration; using Composite.Core.Extensions; +using Composite.Core.WebClient.Renderings; +using Composite.Core.WebClient.Renderings.Page; +using Composite.Search.DocumentSources; namespace Composite.Core.Routing.Pages { @@ -42,8 +45,7 @@ public static PageUrlData PageUrlData /// The PathInfo url part. public static string GetPathInfo() { - var urlData = PageUrlData; - return urlData != null ? urlData.PathInfo : null; + return PageUrlData?.PathInfo; } /// @@ -94,13 +96,22 @@ public override RouteData GetRouteData(HttpContextBase context) string localPath = context.Request.Url.LocalPath; - var urlProvider = PageUrls.UrlProvider; + if (IsPagePreviewPath(localPath) && + PagePreviewContext.TryGetPreviewKey(context.Request, out Guid previewKey)) + { + var page = PagePreviewContext.GetPage(previewKey); + if (page == null) throw new InvalidOperationException("Not preview information found by key: " + previewKey); + + return new RouteData(this, new C1PageRouteHandler(new PageUrlData(page))); + } if (UrlUtils.IsAdminConsoleRequest(localPath) || IsRenderersPath(localPath)) { return null; } + var urlProvider = PageUrls.UrlProvider; + string currentUrl = context.Request.Url.OriginalString; UrlKind urlKind; @@ -195,6 +206,11 @@ private static bool IsRenderersPath(string relativeUrl) return relativeUrl.StartsWith(UrlUtils.RenderersRootPath + "/", true); } + private static bool IsPagePreviewPath(string relativeUrl) + { + return relativeUrl.StartsWith($"{UrlUtils.RenderersRootPath}/PagePreview", true); + } + private RouteData GetRedirectRoute(string url) { return new RouteData(this, new SeoFriendlyRedirectRouteHandler(url)); diff --git a/Composite/Core/WebClient/Renderings/Page/PagePreviewBuilder.cs b/Composite/Core/WebClient/Renderings/Page/PagePreviewBuilder.cs index a5d50c6175..803f44f508 100644 --- a/Composite/Core/WebClient/Renderings/Page/PagePreviewBuilder.cs +++ b/Composite/Core/WebClient/Renderings/Page/PagePreviewBuilder.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Web; -using System.Web.Caching; using Composite.Data.Types; namespace Composite.Core.WebClient.Renderings.Page @@ -13,8 +12,6 @@ namespace Composite.Core.WebClient.Renderings.Page [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 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. Requires IIS to run in "Integrated" pipeline mode. /// @@ -41,19 +38,14 @@ public static string RenderPreview(IPage selectedPage, IList public static string RenderPreview(IPage selectedPage, IList contents, RenderingReason renderingReason) { - HttpContext ctx = HttpContext.Current; - string key = Guid.NewGuid().ToString(); - string query = "previewKey=" + key; - - ctx.Cache.Add(key + "_SelectedPage", selectedPage, null, Cache.NoAbsoluteExpiration, PreviewExpirationTimeSpan, CacheItemPriority.NotRemovable, null); - ctx.Cache.Add(key + "_SelectedContents", contents, null, Cache.NoAbsoluteExpiration, PreviewExpirationTimeSpan, CacheItemPriority.NotRemovable, null); - ctx.Cache.Add(key + "_RenderingReason", renderingReason, null, Cache.NoAbsoluteExpiration, PreviewExpirationTimeSpan, CacheItemPriority.NotRemovable, null); - if (!HttpRuntime.UsingIntegratedPipeline) { throw new InvalidOperationException("IIS classic mode not supported"); } + var previewKey = Guid.NewGuid(); + PagePreviewContext.Save(previewKey, selectedPage, contents, renderingReason); + // 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 @@ -61,13 +53,15 @@ public static string RenderPreview(IPage selectedPage, IList key + "_SelectedPage"; + private static string CacheKey_Contents(Guid key) => key + "_SelectedContents"; + private static string CacheKey_RenderingReason(Guid key) => key + "_RenderingReason"; + + public static void Save(Guid previewKey, IPage selectedPage, IList contents, RenderingReason renderingReason) + { + var cache = HttpRuntime.Cache; + + cache.Add(CacheKey_Page(previewKey), selectedPage, null, Cache.NoAbsoluteExpiration, PreviewExpirationTimeSpan, CacheItemPriority.NotRemovable, null); + cache.Add(CacheKey_Contents(previewKey), contents, null, Cache.NoAbsoluteExpiration, PreviewExpirationTimeSpan, CacheItemPriority.NotRemovable, null); + cache.Add(CacheKey_RenderingReason(previewKey), renderingReason, null, Cache.NoAbsoluteExpiration, PreviewExpirationTimeSpan, CacheItemPriority.NotRemovable, null); + } + + public static bool TryGetPreviewKey(HttpRequest request, out Guid previewKey) + { + return TryGetPreviewKey(request.QueryString, out previewKey); + } + + public static bool TryGetPreviewKey(HttpRequestBase request, out Guid previewKey) + { + return TryGetPreviewKey(request.QueryString, out previewKey); + } + + private static bool TryGetPreviewKey(NameValueCollection queryString, out Guid previewKey) + { + var value = queryString[PreviewKeyUrlParameter]; + if (!string.IsNullOrWhiteSpace(value) && Guid.TryParse(value, out previewKey)) + { + return true; + } + + previewKey = Guid.Empty; + return false; + } + + public static IPage GetPage(Guid previewKey) + => (IPage) HttpRuntime.Cache.Get(CacheKey_Page(previewKey)); + + public static IList GetPageContents(Guid previewKey) + => (IList)HttpRuntime.Cache.Get(CacheKey_Contents(previewKey)); + + public static RenderingReason GetRenderingReason(Guid previewKey) + => (RenderingReason)HttpRuntime.Cache.Get(CacheKey_RenderingReason(previewKey)); + + public static void Remove(Guid previewKey) + { + var cache = HttpRuntime.Cache; + + cache.Remove(CacheKey_Page(previewKey)); + cache.Remove(CacheKey_Contents(previewKey)); + cache.Remove(CacheKey_RenderingReason(previewKey)); + } + } +} diff --git a/Composite/Core/WebClient/Renderings/RenderingContext.cs b/Composite/Core/WebClient/Renderings/RenderingContext.cs index f39e2f3579..84f0a89a4d 100644 --- a/Composite/Core/WebClient/Renderings/RenderingContext.cs +++ b/Composite/Core/WebClient/Renderings/RenderingContext.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text; using System.Threading; @@ -54,7 +54,7 @@ public sealed class RenderingContext: IDisposable private static readonly List _prettifyErrorUrls = new List(); private static int _prettifyErrorCount; - private string _previewKey; + private Guid _previewKey; private IDisposable _pagePerfMeasuring; private string _cachedUrl; private IDisposable _dataScope; @@ -115,7 +115,7 @@ public bool RunResponseHandlers() /// public IEnumerable GetPagePlaceholderContents() { - return PreviewMode ? (IEnumerable)HttpRuntime.Cache.Get(_previewKey + "_SelectedContents") + return PreviewMode ? PagePreviewContext.GetPageContents(_previewKey) : PageManager.GetPlaceholderContent(Page.Id, Page.VersionId); } @@ -204,15 +204,13 @@ private void InitializeFromHttpContextInternal() _pagePerfMeasuring = Profiler.Measure("C1 Page"); } - _previewKey = request.QueryString["previewKey"]; - PreviewMode = !_previewKey.IsNullOrEmpty(); + PreviewMode = PagePreviewContext.TryGetPreviewKey(request, out _previewKey); if (PreviewMode) { - Page = (IPage)HttpRuntime.Cache.Get(_previewKey + "_SelectedPage"); + Page = PagePreviewContext.GetPage(_previewKey); C1PageRoute.PageUrlData = new PageUrlData(Page); - - PageRenderer.RenderingReason = (RenderingReason) HttpRuntime.Cache.Get(_previewKey + "_RenderingReason"); + PageRenderer.RenderingReason = PagePreviewContext.GetRenderingReason(_previewKey); } else { @@ -326,10 +324,7 @@ public void Dispose() if (PreviewMode) { - var cache = HttpRuntime.Cache; - - cache.Remove(_previewKey + "_SelectedPage"); - cache.Remove(_previewKey + "_SelectedContents"); + PagePreviewContext.Remove(_previewKey); } } } From db7a19aca3c5c425dff7a68e277dcf2d51e122c9 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 13 Jan 2020 17:07:50 +0100 Subject: [PATCH 19/38] Disabling caching for unpublished pages; code review fixes --- Composite/AspNet/Caching/OutputCacheHelper.cs | 29 +++++++------------ Composite/AspNet/CmsPageHttpHandler.cs | 28 +++++++++++++----- .../Core/Routing/Pages/C1PageRouteHander.cs | 22 +++++++------- 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/Composite/AspNet/Caching/OutputCacheHelper.cs b/Composite/AspNet/Caching/OutputCacheHelper.cs index 2b69f408c4..3c5a9e8bbd 100644 --- a/Composite/AspNet/Caching/OutputCacheHelper.cs +++ b/Composite/AspNet/Caching/OutputCacheHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; @@ -24,17 +24,13 @@ static OutputCacheHelper() { CacheabilityFieldInfo = typeof(HttpCachePolicy).GetField("_cacheability", BindingFlags.Instance | BindingFlags.NonPublic); - _outputCacheProfiles = new Dictionary(); + var section = WebConfigurationManager.OpenWebConfiguration(HostingEnvironment.ApplicationVirtualPath) + .GetSection("system.web/caching/outputCacheSettings"); - var settings = WebConfigurationManager.OpenWebConfiguration(HostingEnvironment.ApplicationVirtualPath) - .GetSection("system.web/caching/outputCacheSettings") as OutputCacheSettingsSection; - - if (settings != null) + if (section is OutputCacheSettingsSection settings) { - foreach (OutputCacheProfile profile in settings.OutputCacheProfiles) - { - _outputCacheProfiles[profile.Name] = profile; - } + _outputCacheProfiles = settings.OutputCacheProfiles.OfType() + .ToDictionary(_ => _.Name); } } @@ -153,37 +149,34 @@ static OutputCacheProvider GetCacheProvider(HttpContext context) } - public static bool ResponseCachebale(HttpContext context) + public static bool ResponseCacheable(HttpContext context) { if (context.Response.StatusCode != 200) { return false; } -#if !DEBUG - var cacheability = GetPageCacheablity(context); + var cacheability = GetPageCacheability(context); return cacheability > HttpCacheability.NoCache; -#endif - return true; } - private static HttpCacheability GetPageCacheablity(HttpContext context) + private static HttpCacheability GetPageCacheability(HttpContext context) => (HttpCacheability)CacheabilityFieldInfo.GetValue(context.Response.Cache); public static void InitializeFullPageCaching(HttpContext context) { - using (var page = new CachableEmptyPage()) + using (var page = new CacheableEmptyPage()) { page.ProcessRequest(context); } } - private class CachableEmptyPage : Page + private class CacheableEmptyPage : Page { protected override void FrameworkInitialize() { diff --git a/Composite/AspNet/CmsPageHttpHandler.cs b/Composite/AspNet/CmsPageHttpHandler.cs index 4ba35b8673..49b953f023 100644 --- a/Composite/AspNet/CmsPageHttpHandler.cs +++ b/Composite/AspNet/CmsPageHttpHandler.cs @@ -20,22 +20,28 @@ public void ProcessRequest(HttpContext context) { OutputCacheHelper.InitializeFullPageCaching(context); - bool cachingEnabled = OutputCacheHelper.TryGetCacheKey(context, out string cacheKey); - using (var renderingContext = RenderingContext.InitializeFromHttpContext()) { - var functionContext = PageRenderer.GetPageRenderFunctionContextContainer(); - - XDocument document; + bool cachingEnabled = false; + string cacheKey = null; DonutCacheEntry cacheEntry = null; - if (cachingEnabled) + + bool consoleUserLoggedIn = Composite.C1Console.Security.UserValidationFacade.IsLoggedIn(); + + // "Donut caching" is enabled for logged in users, only if profiling is enabled as well. + if (!renderingContext.CachingDisabled + && (!consoleUserLoggedIn || renderingContext.ProfilingEnabled)) { + cachingEnabled = OutputCacheHelper.TryGetCacheKey(context, out cacheKey); using (Profiler.Measure("Cache lookup")) { cacheEntry = OutputCacheHelper.GetFromCache(context, cacheKey); } } + XDocument document; + var functionContext = PageRenderer.GetPageRenderFunctionContextContainer(); + bool allFunctionsExecuted = false; bool preventResponseCaching = false; @@ -65,10 +71,10 @@ public void ProcessRequest(HttpContext context) { document = slimRenderer.Render(renderingContext.PageContentToRender, functionContext); } - + allFunctionsExecuted = PageRenderer.ExecuteCacheableFunctions(document.Root, functionContext); - if (cachingEnabled && !allFunctionsExecuted && OutputCacheHelper.ResponseCachebale(context)) + if (cachingEnabled && !allFunctionsExecuted && OutputCacheHelper.ResponseCacheable(context)) { preventResponseCaching = true; @@ -129,6 +135,12 @@ public void ProcessRequest(HttpContext context) context.Response.Cache.SetNoServerCaching(); } + // Disabling ASP.NET cache if there's a logged-in user + if (consoleUserLoggedIn) + { + context.Response.Cache.SetCacheability(HttpCacheability.NoCache); + } + // Inserting performance profiling information if (renderingContext.ProfilingEnabled) { diff --git a/Composite/Core/Routing/Pages/C1PageRouteHander.cs b/Composite/Core/Routing/Pages/C1PageRouteHander.cs index 3d52c0f17f..014fb10211 100644 --- a/Composite/Core/Routing/Pages/C1PageRouteHander.cs +++ b/Composite/Core/Routing/Pages/C1PageRouteHander.cs @@ -91,6 +91,17 @@ public IHttpHandler GetHttpHandler(RequestContext requestContext) context.RewritePath(filePath, pathInfo, queryString); } + if (_handlerType == null && GlobalSettingsFacade.OmitAspNetWebFormsSupport) + { + var page = _pageUrlData.GetPage() + ?? throw new HttpException(404, "Page not found - either this page has not been published yet or it has been deleted."); + + if (IsSlimPageRenderer(page.TemplateId)) + { + return new CmsPageHttpHandler(); + } + } + // Disabling ASP.NET cache if there's a logged-in user if (Composite.C1Console.Security.UserValidationFacade.IsLoggedIn()) { @@ -102,17 +113,6 @@ public IHttpHandler GetHttpHandler(RequestContext requestContext) return (IHttpHandler)Activator.CreateInstance(_handlerType); } - if (GlobalSettingsFacade.OmitAspNetWebFormsSupport) - { - var page = _pageUrlData.GetPage() - ?? throw new HttpException(404, "Page not found - either this page has not been published yet or it has been deleted."); - - if (IsSlimPageRenderer(page.TemplateId)) - { - return new CmsPageHttpHandler(); - } - } - return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(PageHandlerVirtualPath, typeof(Page)); } From cca31ee8938f0ed63b5fa1e5b019f5a19b6e9162 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 14 Jan 2020 13:37:07 +0100 Subject: [PATCH 20/38] Performance profiler - logging individual IPageContentFilter execution time --- .../Core/WebClient/Renderings/Page/PageRenderer.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs index ec81ff8ae3..7b357f9197 100644 --- a/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs +++ b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs @@ -244,9 +244,15 @@ internal static void ProcessXhtmlDocument(XhtmlDocument xhtmlDocument, IPage pag var filters = ServiceLocator.GetServices().OrderBy(f => f.Order).ToList(); if (filters.Any()) { - using (Profiler.Measure("Executing custom filters")) + using (Profiler.Measure("Executing page content filters")) { - filters.ForEach(_ => _.Filter(xhtmlDocument, page)); + filters.ForEach(filter => + { + using (Profiler.Measure($"Filter: {filter.GetType().FullName}")) + { + filter.Filter(xhtmlDocument, page); + } + }); } } } From 674d98620610db4d973db0eb5d2e2104b92d06e0 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 28 Jan 2020 15:00:51 +0100 Subject: [PATCH 21/38] Fix #694 Updating the list of self-closing HTML elements --- .../Page/XElementToAspNetExtensions.cs | 27 ++++++---------- Composite/Core/Xml/XhtmlPrettifier.cs | 32 ++++++++++++------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs b/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs index ec038573c6..05cd4d694f 100644 --- a/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs +++ b/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs @@ -25,6 +25,12 @@ public static class XElementToAspNetExtensions private static readonly XName XName_Title = Namespaces.Xhtml + "title"; private static readonly XName XName_Meta = Namespaces.Xhtml + "meta"; + private static readonly HashSet VoidElements = new HashSet + { + "area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", + "source", "track", "wbr" + }; + /// public static Control AsAspNetControl(this XhtmlDocument xhtmlDocument) { @@ -169,23 +175,10 @@ private static XElement CopyWithoutNamespace(XElement source, XNamespace namespa private static bool IsHtmlControlElement(XElement element) { var name = element.Name; - string xnamespace = element.Name.Namespace.NamespaceName; - if (xnamespace == Namespaces.Xhtml.NamespaceName || xnamespace == string.Empty) - { - switch (name.LocalName) - { - case "input": - case "base": - case "param": - case "img": - case "br": - case "hr": - return false; - default: - return true; - } - } - return false; + string xNamespace = element.Name.Namespace.NamespaceName; + + return (xNamespace == Namespaces.Xhtml.NamespaceName || xNamespace == string.Empty) + && !VoidElements.Contains(name.LocalName); } diff --git a/Composite/Core/Xml/XhtmlPrettifier.cs b/Composite/Core/Xml/XhtmlPrettifier.cs index fd13148db6..0a7422b813 100644 --- a/Composite/Core/Xml/XhtmlPrettifier.cs +++ b/Composite/Core/Xml/XhtmlPrettifier.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -95,18 +95,26 @@ public static class XhtmlPrettifier private static readonly HashSet SelfClosingElements = new HashSet(new [] { - new NamespaceName { Name = "br", Namespace = "" }, - new NamespaceName { Name = "hr", Namespace = "" }, - new NamespaceName { Name = "input", Namespace = "" }, - new NamespaceName { Name = "frame", Namespace = "" }, - new NamespaceName { Name = "img", Namespace = "" }, - new NamespaceName { Name = "area", Namespace = "" }, - new NamespaceName { Name = "meta", Namespace = "" }, - new NamespaceName { Name = "link", Namespace = "" }, - new NamespaceName { Name = "col", Namespace = "" }, - new NamespaceName { Name = "base", Namespace = "" }, + // "Void elements" defined in HTML5 + new NamespaceName { Name = "area", Namespace = "" }, + new NamespaceName { Name = "base", Namespace = "" }, + new NamespaceName { Name = "br", Namespace = "" }, + new NamespaceName { Name = "col", Namespace = "" }, + new NamespaceName { Name = "command", Namespace = "" }, + new NamespaceName { Name = "embed", Namespace = "" }, + new NamespaceName { Name = "hr", Namespace = "" }, + new NamespaceName { Name = "img", Namespace = "" }, + new NamespaceName { Name = "input", Namespace = "" }, + new NamespaceName { Name = "keygen", Namespace = "" }, + new NamespaceName { Name = "link", Namespace = "" }, + new NamespaceName { Name = "meta", Namespace = "" }, + new NamespaceName { Name = "param", Namespace = "" }, + new NamespaceName { Name = "source", Namespace = "" }, + new NamespaceName { Name = "track", Namespace = "" }, + new NamespaceName { Name = "wbr", Namespace = "" }, + // Obsolete element types new NamespaceName { Name = "basefont", Namespace = "" }, - new NamespaceName { Name = "param", Namespace = "" } + new NamespaceName { Name = "frame", Namespace = "" } }); From e10f4b42d9ba8230906734d16752bce862c9adec Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Fri, 7 Feb 2020 17:33:42 +0200 Subject: [PATCH 22/38] throw httpError 404 instead return 404 --- Composite/Core/WebClient/Renderings/RenderingContext.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Composite/Core/WebClient/Renderings/RenderingContext.cs b/Composite/Core/WebClient/Renderings/RenderingContext.cs index 84f0a89a4d..62fe2135f4 100644 --- a/Composite/Core/WebClient/Renderings/RenderingContext.cs +++ b/Composite/Core/WebClient/Renderings/RenderingContext.cs @@ -296,8 +296,7 @@ public bool PreRenderRedirectCheck() return true; } - httpContext.Response.StatusCode = 404; - httpContext.Response.End(); + throw new HttpException(404, $"Page not found"); } // Setting 404 response code if it is a request to a custom "Page not found" page From 44c7ea5376220585b37275b92917ef9894f07d2d Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 11 Feb 2020 11:54:27 +0100 Subject: [PATCH 23/38] Fixing a null ref exception when caching profile 'C1Page' is disabled --- Composite/AspNet/CmsPageHttpHandler.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Composite/AspNet/CmsPageHttpHandler.cs b/Composite/AspNet/CmsPageHttpHandler.cs index 49b953f023..56a57043bf 100644 --- a/Composite/AspNet/CmsPageHttpHandler.cs +++ b/Composite/AspNet/CmsPageHttpHandler.cs @@ -33,9 +33,12 @@ public void ProcessRequest(HttpContext context) && (!consoleUserLoggedIn || renderingContext.ProfilingEnabled)) { cachingEnabled = OutputCacheHelper.TryGetCacheKey(context, out cacheKey); - using (Profiler.Measure("Cache lookup")) + if (cachingEnabled) { - cacheEntry = OutputCacheHelper.GetFromCache(context, cacheKey); + using (Profiler.Measure("Cache lookup")) + { + cacheEntry = OutputCacheHelper.GetFromCache(context, cacheKey); + } } } From c0da6d84d0004e88ddefab60f6cb2a04850ad8b8 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 13 Feb 2020 12:58:23 +0100 Subject: [PATCH 24/38] Fix #720 Console search doesn't work when SqlDataProvider is used --- .../Data/Types/IUserUserGroupRelation.cs | 3 +- .../Endpoint/ConsoleSearchRpcService.cs | 208 +++++++++--------- 2 files changed, 108 insertions(+), 103 deletions(-) diff --git a/Composite/Data/Types/IUserUserGroupRelation.cs b/Composite/Data/Types/IUserUserGroupRelation.cs index 0fa6ffe1e2..ab5de74e95 100644 --- a/Composite/Data/Types/IUserUserGroupRelation.cs +++ b/Composite/Data/Types/IUserUserGroupRelation.cs @@ -1,4 +1,4 @@ -using System; +using System; using Composite.Data.Hierarchy; using Composite.Data.Hierarchy.DataAncestorProviders; @@ -9,6 +9,7 @@ namespace Composite.Data.Types /// This data interface represents a user relation to a user group in C1 CMS. This can be used to query user group members through a . /// [AutoUpdateble] + [Caching(CachingType.Full)] [KeyPropertyName(0, "UserId")] [KeyPropertyName(1, "UserGroupId")] [DataScope(DataScopeIdentifier.PublicName)] diff --git a/Composite/Plugins/Search/Endpoint/ConsoleSearchRpcService.cs b/Composite/Plugins/Search/Endpoint/ConsoleSearchRpcService.cs index 54f96a4c2e..e8f51791f0 100644 --- a/Composite/Plugins/Search/Endpoint/ConsoleSearchRpcService.cs +++ b/Composite/Plugins/Search/Endpoint/ConsoleSearchRpcService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -11,6 +11,7 @@ using Composite.Core.Application; using Composite.Core.Linq; using Composite.Core.ResourceSystem; +using Composite.Core.Threading; using Composite.Core.WebClient; using Composite.Core.WebClient.Services.WampRouter; using Composite.Data; @@ -55,131 +56,134 @@ public async Task QueryAsync(ConsoleSearchQuery query) { if (_searchProvider == null || query == null) return null; - Thread.CurrentThread.CurrentCulture = UserSettings.CultureInfo; - Thread.CurrentThread.CurrentUICulture = UserSettings.C1ConsoleUiLanguage; + using (ThreadDataManager.EnsureInitialize()) + { + Thread.CurrentThread.CurrentCulture = UserSettings.CultureInfo; + Thread.CurrentThread.CurrentUICulture = UserSettings.C1ConsoleUiLanguage; - var documentSources = _docSourceProviders.SelectMany(dsp => dsp.GetDocumentSources()).ToList(); - var allFields = documentSources.SelectMany(ds => ds.CustomFields).ToList(); + var documentSources = _docSourceProviders.SelectMany(dsp => dsp.GetDocumentSources()).ToList(); + var allFields = documentSources.SelectMany(ds => ds.CustomFields).ToList(); - var facetFields = RemoveDuplicateKeys( - allFields - .Where(f => f.FacetedSearchEnabled && f.Label != null), - f => f.Name).ToList(); + var facetFields = RemoveDuplicateKeys( + allFields + .Where(f => f.FacetedSearchEnabled && f.Label != null), + f => f.Name).ToList(); - if (string.IsNullOrEmpty(query.Text)) - { - return new ConsoleSearchResult + if (string.IsNullOrEmpty(query.Text)) { - QueryText = string.Empty, - FacetFields = EmptyFacetsFromSelections(query, facetFields), - TotalHits = 0 - }; - } + return new ConsoleSearchResult + { + QueryText = string.Empty, + FacetFields = EmptyFacetsFromSelections(query, facetFields), + TotalHits = 0 + }; + } - var selections = new List(); - if (query.Selections != null) - { - foreach (var selection in query.Selections) + var selections = new List(); + if (query.Selections != null) { - string fieldName = ExtractFieldName(selection.FieldName); + foreach (var selection in query.Selections) + { + string fieldName = ExtractFieldName(selection.FieldName); - var field = allFields.Where(f => f.Facet != null) - .FirstOrDefault(f => f.Name == fieldName); - Verify.IsNotNull(field, $"Failed to find a facet field by name '{fieldName}'"); + var field = allFields.Where(f => f.Facet != null) + .FirstOrDefault(f => f.Name == fieldName); + Verify.IsNotNull(field, $"Failed to find a facet field by name '{fieldName}'"); - selections.Add(new SearchQuerySelection - { - FieldName = fieldName, - Values = selection.Values, - Operation = field.Facet.FacetType == FacetType.SingleValue - ? SearchQuerySelectionOperation.Or - : SearchQuerySelectionOperation.And - }); + selections.Add(new SearchQuerySelection + { + FieldName = fieldName, + Values = selection.Values, + Operation = field.Facet.FacetType == FacetType.SingleValue + ? SearchQuerySelectionOperation.Or + : SearchQuerySelectionOperation.And + }); + } } - } - var sortOptions = new List(); - if (!string.IsNullOrEmpty(query.SortBy)) - { - string sortByFieldName = ExtractFieldName(query.SortBy); + var sortOptions = new List(); + if (!string.IsNullOrEmpty(query.SortBy)) + { + string sortByFieldName = ExtractFieldName(query.SortBy); - var sortTermsAs = allFields - .Where(f => f.Name == sortByFieldName && f.Preview != null && f.Preview.Sortable) - .Select(f => f.Preview.SortTermsAs) - .FirstOrDefault(); + var sortTermsAs = allFields + .Where(f => f.Name == sortByFieldName && f.Preview != null && f.Preview.Sortable) + .Select(f => f.Preview.SortTermsAs) + .FirstOrDefault(); - sortOptions.Add(new SearchQuerySortOption(sortByFieldName, query.SortInReverseOrder, sortTermsAs)); - } + sortOptions.Add(new SearchQuerySortOption(sortByFieldName, query.SortInReverseOrder, sortTermsAs)); + } - var culture = !string.IsNullOrEmpty(query.CultureName) - ? new CultureInfo(query.CultureName) - : UserSettings.ActiveLocaleCultureInfo; - - var searchQuery = new SearchQuery(query.Text, culture) - { - Facets = facetFields.Select(f => new KeyValuePair(f.Name, f.Facet)).ToList(), - Selection = selections, - SortOptions = sortOptions - }; + var culture = !string.IsNullOrEmpty(query.CultureName) + ? new CultureInfo(query.CultureName) + : UserSettings.ActiveLocaleCultureInfo; + + var searchQuery = new SearchQuery(query.Text, culture) + { + Facets = facetFields.Select(f => new KeyValuePair(f.Name, f.Facet)).ToList(), + Selection = selections, + SortOptions = sortOptions + }; - searchQuery.FilterByUser(UserSettings.Username); - searchQuery.AddFieldFacet(DocumentFieldNames.Source); + searchQuery.FilterByUser(UserSettings.Username); + searchQuery.AddFieldFacet(DocumentFieldNames.Source); - var result = await _searchProvider.SearchAsync(searchQuery); + var result = await _searchProvider.SearchAsync(searchQuery); - var items = result.Items.Evaluate(); - if (!items.Any()) - { - return new ConsoleSearchResult + var items = result.Items.Evaluate(); + if (!items.Any()) { - QueryText = query.Text, - FacetFields = EmptyFacetsFromSelections(query, facetFields), - TotalHits = 0 - }; - } + return new ConsoleSearchResult + { + QueryText = query.Text, + FacetFields = EmptyFacetsFromSelections(query, facetFields), + TotalHits = 0 + }; + } - var documents = items.Select(m => m.Document); + var documents = items.Select(m => m.Document); - HashSet dataSourceNames; - Facet[] dsFacets; - if (result.Facets != null && result.Facets.TryGetValue(DocumentFieldNames.Source, out dsFacets)) - { - dataSourceNames = new HashSet(dsFacets.Select(v => v.Value)); - } - else - { - Log.LogWarning(nameof(ConsoleSearchRpcService), "The search provider did not return the list of document sources"); - dataSourceNames = new HashSet(documents.Select(d => d.Source).Distinct()); - } + HashSet dataSourceNames; + Facet[] dsFacets; + if (result.Facets != null && result.Facets.TryGetValue(DocumentFieldNames.Source, out dsFacets)) + { + dataSourceNames = new HashSet(dsFacets.Select(v => v.Value)); + } + else + { + Log.LogWarning(nameof(ConsoleSearchRpcService), "The search provider did not return the list of document sources"); + dataSourceNames = new HashSet(documents.Select(d => d.Source).Distinct()); + } - var dataSources = documentSources.Where(d => dataSourceNames.Contains(d.Name)).ToList(); - var previewFields = RemoveDuplicateKeys( - dataSources - .SelectMany(ds => ds.CustomFields) - .Where(f => f.FieldValuePreserved), - f => f.Name).ToList(); + var dataSources = documentSources.Where(d => dataSourceNames.Contains(d.Name)).ToList(); + var previewFields = RemoveDuplicateKeys( + dataSources + .SelectMany(ds => ds.CustomFields) + .Where(f => f.FieldValuePreserved), + f => f.Name).ToList(); - using (new DataConnection(culture)) - { - return new ConsoleSearchResult + using (new DataConnection(culture)) { - QueryText = query.Text, - Columns = previewFields.Select(pf => new ConsoleSearchResultColumn - { - FieldName = MakeFieldNameJsFriendly(pf.Name), - Label = StringResourceSystemFacade.ParseString(pf.Label), - Sortable = pf.Preview.Sortable - }).ToArray(), - Rows = documents.Select(doc => new ConsoleSearchResultRow + return new ConsoleSearchResult { - Label = doc.Label, - Url = GetFocusUrl(doc.SerializedEntityToken), - Values = GetPreviewValues(doc, previewFields) - }).ToArray(), - FacetFields = GetFacets(result, facetFields), - TotalHits = result.TotalHits - }; + QueryText = query.Text, + Columns = previewFields.Select(pf => new ConsoleSearchResultColumn + { + FieldName = MakeFieldNameJsFriendly(pf.Name), + Label = StringResourceSystemFacade.ParseString(pf.Label), + Sortable = pf.Preview.Sortable + }).ToArray(), + Rows = documents.Select(doc => new ConsoleSearchResultRow + { + Label = doc.Label, + Url = GetFocusUrl(doc.SerializedEntityToken), + Values = GetPreviewValues(doc, previewFields) + }).ToArray(), + FacetFields = GetFacets(result, facetFields), + TotalHits = result.TotalHits + }; + } } } From edf8783f9d5c16d3fc145a66a61b912b5642df18 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 18 Feb 2020 15:48:13 +0100 Subject: [PATCH 25/38] Fixing some errors that occur when running C1 from a .NET console application --- .../IApplicationHostExtensionMethods.cs | 16 ++++++++++++++-- .../WebClient/ApplicationLevelEventHandlers.cs | 4 +++- Composite/GlobalInitializerFacade.cs | 2 +- .../RazorFunctionProvider.cs | 8 +++++++- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Composite/Core/Extensions/IApplicationHostExtensionMethods.cs b/Composite/Core/Extensions/IApplicationHostExtensionMethods.cs index 036bcaf5ae..9588551d63 100644 --- a/Composite/Core/Extensions/IApplicationHostExtensionMethods.cs +++ b/Composite/Core/Extensions/IApplicationHostExtensionMethods.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; using System.Web.Hosting; @@ -12,10 +12,22 @@ public static class IApplicationHostExtensionMethods { static readonly PropertyInfo _shutdownInitiatedPropertyInfo = typeof(HostingEnvironment).GetProperty("ShutdownInitiated", BindingFlags.NonPublic | BindingFlags.Static); + private static bool _processExit; + + static IApplicationHostExtensionMethods() + { + AppDomain.CurrentDomain.ProcessExit += (s, a) => _processExit = true; + } + /// public static bool ShutdownInitiated(this IApplicationHost host) { - return (bool)_shutdownInitiatedPropertyInfo.GetValue(null, new object[0]); + if (!HostingEnvironment.IsHosted) + { + return _processExit; + } + + return (bool)_shutdownInitiatedPropertyInfo.GetValue(null, null); } } } diff --git a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs index 081dcdf881..3804e175b4 100644 --- a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs +++ b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs @@ -402,8 +402,10 @@ private static void LogShutDownReason() System.Reflection.BindingFlags.GetField, null, runtime, null); + shutDownMessage = (shutDownMessage ?? "null").Replace("\n", " \n"); + Log.LogVerbose("RGB(250,50,50)ASP.NET Shut Down", - $"_shutDownMessage=\n{shutDownMessage.Replace("\n", " \n")}\n\n_shutDownStack=\n{shutDownStack}"); + $"_shutDownMessage=\n{shutDownMessage}\n\n_shutDownStack=\n{shutDownStack}"); } } diff --git a/Composite/GlobalInitializerFacade.cs b/Composite/GlobalInitializerFacade.cs index 7def1b0e4c..33cad42683 100644 --- a/Composite/GlobalInitializerFacade.cs +++ b/Composite/GlobalInitializerFacade.cs @@ -257,7 +257,7 @@ private static void DoInitialize() Log.LogVerbose(LogTitle, "Starting initialization of administrative secondaries"); - if (SystemSetupFacade.IsSystemFirstTimeInitialized && !SystemSetupFacade.SetupIsRunning) + if (SystemSetupFacade.IsSystemFirstTimeInitialized && !SystemSetupFacade.SetupIsRunning && HostingEnvironment.IsHosted) { using (new LogExecutionTime(LogTitle, "Initializing workflow runtime")) { diff --git a/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorFunctionProvider.cs b/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorFunctionProvider.cs index 3f2ab541e0..e23bab72fa 100644 --- a/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorFunctionProvider.cs +++ b/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorFunctionProvider.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Web.Hosting; using System.Web.WebPages; using Composite.AspNet.Razor; using Composite.Core; @@ -22,6 +23,11 @@ public RazorFunctionProvider(string name, string folder) : base(name, folder) { protected override IFunction InstantiateFunction(string virtualPath, string @namespace, string name) { + if (!HostingEnvironment.IsHosted) + { + return null; + } + WebPageBase razorPage; using (BuildManagerHelper.DisableUrlMetadataCachingScope()) { From 50b09a04014a062c4c0fd7a3a5e696d4f8473499 Mon Sep 17 00:00:00 2001 From: vadym-hyryn <57723696+vadym-hyryn@users.noreply.github.com> Date: Thu, 20 Feb 2020 18:57:37 +0200 Subject: [PATCH 26/38] Fixing a bug with preselector (#725) --- .../TreeSelectorDialogPageBinding.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Website/Composite/content/dialogs/treeselector/TreeSelectorDialogPageBinding.js b/Website/Composite/content/dialogs/treeselector/TreeSelectorDialogPageBinding.js index 7cb620f238..8a981b13b9 100644 --- a/Website/Composite/content/dialogs/treeselector/TreeSelectorDialogPageBinding.js +++ b/Website/Composite/content/dialogs/treeselector/TreeSelectorDialogPageBinding.js @@ -477,12 +477,16 @@ TreeSelectorDialogPageBinding.prototype.onAfterPageInitialize = function () { TreeSelectorDialogPageBinding.superclass.onAfterPageInitialize.call(this); - this._treeBinding.focus(); - - if (this._selectedToken) - this._treeBinding._focusTreeNodeByEntityToken(this._selectedToken); - else - this._treeBinding.selectDefault(); + var treeBinding = this._treeBinding; + var token = this._selectedToken; + + setTimeout(function () { + treeBinding.focus(); + if (token) + treeBinding._focusTreeNodeByEntityToken(token); + else + treeBinding.selectDefault(); + }, 0); } /** From 0280e8e72014e2612b041609551879c1488aceb7 Mon Sep 17 00:00:00 2001 From: vadym-hyryn <57723696+vadym-hyryn@users.noreply.github.com> Date: Mon, 24 Feb 2020 16:10:18 +0200 Subject: [PATCH 27/38] Fixing a bug with treeselector and simplifying filter usage (#721) * Bug fixing and implementic logic changes The bug was in the following. Method GetPageIdFromDataToken of GetPageId class was parsing page ID from entity token. But depending on different situations (add datafolder element, edit datafolder element, add metadata data) there will be expecting different entity tokens. I've implemented all these 3 cases, so now filtering for datafolders will work as well. If there are another cases except of datafolders and metadata fields, they should be also implemented. Initially in the widget was an option to add page selector and to configure different filtering options: Level 1 (home page), level 2, level 3, level 4. But as we have to display a tree element to the end user, and a tree element should have some root, and on level 2 there could be 2,3,4,5 subtrees, there was no way to use these options level 2,3 and 4 and they didn't work as well. So there left only 1 available and possible for use option: level 1 - to show web pages only of some certain webpage. To leave a combobox with only 1 option "level 1" seemed not very good, therefore to PageReferenceSelectorWidgetFunctionBase instead of configuring option with levels was added a bool option: to filter or not to filter pages by active website. Because a standart bool widget provider was used, class HomePageSelectorWidgetFunction is not necessary anymore so it was deleted. As GetPageId returnt only ID of a root element, and all another code was removed, so I renamed it to GetHomePageId to avoid misunderstanding. GetRootPageId would be better as for me, but in all places was used a word "home", wherefore I used the same word and style * Revert "Bug fixing and implementic logic changes" This reverts commit 6c328d9b93b24693760edf12ce05a48db76d65fe. * Restoring GetPageId * Review updates * Review updates * Restoring filtering by GetPageId with Level1 (#727) After discussion with Taras decided to keep functionality as before (choosing GetPageId and selecting Level1) and to affect only bug fixes, without direct bool filter --- .../Pages/GetPageIdFunction.cs | 40 ++++++++++++++----- ...PageReferenceSelectorWidgetFunctionBase.cs | 5 +-- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs b/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs index 991230a23f..8ee00a01c8 100644 --- a/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs +++ b/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs @@ -11,6 +11,7 @@ using Composite.Core.WebClient.Renderings.Page; using Composite.Data; using Composite.Data.Types; +using Composite.C1Console.Security; namespace Composite.Plugins.Functions.FunctionProviders.StandardFunctionProvider.Pages { @@ -23,7 +24,7 @@ public GetPageIdFunction(EntityTokenFactory entityTokenFactory) public override object Execute(ParameterList parameters, FunctionContextContainer context) { - if (parameters.TryGetParameter("SitemapScope", out var sitemapScope) == false) + if (!parameters.TryGetParameter(nameof(SitemapScope), out var sitemapScope)) { sitemapScope = SitemapScope.Current; } @@ -70,7 +71,7 @@ private Guid GetCurrentPageId() private Guid? GetCurrentPageIdFromHttpContext() { var entityToken = HttpContext.Current?.Items[ActionExecutorFacade.HttpContextItem_EntityToken]; - return GetPageIdFromDataToken(entityToken as DataEntityToken); + return GetPageIdFromEntityToken(entityToken as EntityToken); } private Guid? GetCurrentPageIdFromFormFlow() @@ -78,23 +79,40 @@ private Guid GetCurrentPageId() var currentFormTreeCompiler = FormFlowUiDefinitionRenderer.CurrentFormTreeCompiler; if (currentFormTreeCompiler.BindingObjects.TryGetValue(FormsWorkflow.EntityTokenKey, out var entityToken)) { - return GetPageIdFromDataToken(entityToken as DataEntityToken); + return GetPageIdFromEntityToken(entityToken as EntityToken); } return null; } - private Guid? GetPageIdFromDataToken(DataEntityToken entityToken) + private Guid? GetPageIdFromEntityToken(EntityToken entityToken) { - if (entityToken == null) + if (entityToken is null) + { return null; + } - if (Type.GetType(entityToken.Type, throwOnError: false) != typeof(IPage)) - return null; + Guid pageId = Guid.Empty; - var data = entityToken.Data as IPage; - var pageId = data?.Id; - return pageId == Guid.Empty ? null : pageId; + if (entityToken is DataEntityToken dataEntityToken) + { + //appears while adding a metadata element + if (dataEntityToken.Data is IPage page) + { + pageId = page.Id; + } + //appears while editing a datafolder element + else if (dataEntityToken.Data is IPageRelatedData pageRelatedData) + { + pageId = pageRelatedData.PageId; + } + } + //appears while adding a datafolder element + else if (typeof(IPage).IsAssignableFrom(Type.GetType(entityToken.Type, throwOnError: false))) + { + Guid.TryParse(entityToken?.Id, out pageId); + } + return pageId == Guid.Empty ? null : (Guid?)pageId; } @@ -106,7 +124,7 @@ protected override IEnumerable StandardFunctio this.GetType(), "PageAssociationRestrictions", "Key", "Value", false, true); yield return new StandardFunctionParameterProfile( - "SitemapScope", + nameof(SitemapScope), typeof(SitemapScope), false, new ConstantValueProvider(SitemapScope.Current), diff --git a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunctionBase.cs b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunctionBase.cs index 609dc81993..c1f3639565 100644 --- a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunctionBase.cs +++ b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunctionBase.cs @@ -1,12 +1,11 @@ -using System; -using System.Xml.Linq; -using Composite.C1Console.Elements; using Composite.Core.Types; using Composite.Core.Xml; using Composite.Data.Types; using Composite.Functions; using Composite.Plugins.Elements.ElementProviders.PageElementProvider; using Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.Foundation; +using System; +using System.Xml.Linq; namespace Composite.Plugins.Functions.WidgetFunctionProviders.StandardWidgetFunctionProvider.DataReference { From 1c7d60cc157a2254c65257a5919060ea438b3e48 Mon Sep 17 00:00:00 2001 From: "vadym.hyryn" Date: Fri, 21 Feb 2020 15:17:32 +0200 Subject: [PATCH 28/38] Fixing a bug with language switcher User group active locales were not deleting. Also add, as I believe, missed part (makeflush) --- Composite/Core/Localization/LocalizationFacade.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Composite/Core/Localization/LocalizationFacade.cs b/Composite/Core/Localization/LocalizationFacade.cs index 97225512fd..cc5d010394 100644 --- a/Composite/Core/Localization/LocalizationFacade.cs +++ b/Composite/Core/Localization/LocalizationFacade.cs @@ -367,12 +367,26 @@ public static void RemoveLocale(CultureInfo cultureInfo, bool makeFlush = true) UserSettings.RemoveActiveLocaleCultureInfo(username, cultureInfo); } + IEnumerable userGroupsLocales = DataFacade.GetData(); + foreach(IUserGroupActiveLocale el in userGroupsLocales) + { + if (el.CultureName == cultureName) + { + DataFacade.Delete(el); + } + } + DataFacade.Delete(systemActiveLocale); transactionScope.Complete(); } DynamicTypeManager.RemoveLocale(cultureInfo); + + if (makeFlush) + { + C1Console.Events.GlobalEventSystemFacade.FlushTheSystem(false); + } } } } From 2a8c5d8deecd887f8fe9ab9bb68a5dd5b7fe6d20 Mon Sep 17 00:00:00 2001 From: "vadym.hyryn" Date: Tue, 25 Feb 2020 13:42:04 +0200 Subject: [PATCH 29/38] Review updates --- Composite/Core/Localization/LocalizationFacade.cs | 9 --------- Composite/Data/Types/IUserGroupActiveLocale.cs | 1 + 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Composite/Core/Localization/LocalizationFacade.cs b/Composite/Core/Localization/LocalizationFacade.cs index cc5d010394..aa8cc0af47 100644 --- a/Composite/Core/Localization/LocalizationFacade.cs +++ b/Composite/Core/Localization/LocalizationFacade.cs @@ -367,15 +367,6 @@ public static void RemoveLocale(CultureInfo cultureInfo, bool makeFlush = true) UserSettings.RemoveActiveLocaleCultureInfo(username, cultureInfo); } - IEnumerable userGroupsLocales = DataFacade.GetData(); - foreach(IUserGroupActiveLocale el in userGroupsLocales) - { - if (el.CultureName == cultureName) - { - DataFacade.Delete(el); - } - } - DataFacade.Delete(systemActiveLocale); transactionScope.Complete(); diff --git a/Composite/Data/Types/IUserGroupActiveLocale.cs b/Composite/Data/Types/IUserGroupActiveLocale.cs index 74bc7923e6..dbe63c9a28 100644 --- a/Composite/Data/Types/IUserGroupActiveLocale.cs +++ b/Composite/Data/Types/IUserGroupActiveLocale.cs @@ -36,6 +36,7 @@ public interface IUserGroupActiveLocale : IData [StringSizeValidator(2, 16)] [StoreFieldType(PhysicalStoreFieldType.String, 16)] [ImmutableFieldId("{ddc38768-16e8-444c-a982-80f2a55b0b75}")] + [ForeignKey(typeof(ISystemActiveLocale), nameof(ISystemActiveLocale.CultureName), AllowCascadeDeletes = true)] string CultureName { get; set; } } } From e383bc67ac164dc17d904496b305efd0418f5a0e Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 5 Mar 2020 15:38:21 +0100 Subject: [PATCH 30/38] DynamicBuildManagerTypeManagerTypeHandler - fixing errors when running C1 from a console app --- ...namicBuildManagerTypeManagerTypeHandler.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Composite/Plugins/Types/TypeManagerTypeHandler/DynamicBuildManagerTypeManagerTypeHandler/DynamicBuildManagerTypeManagerTypeHandler.cs b/Composite/Plugins/Types/TypeManagerTypeHandler/DynamicBuildManagerTypeManagerTypeHandler/DynamicBuildManagerTypeManagerTypeHandler.cs index 8d9b4cfa16..e301c1a5a6 100644 --- a/Composite/Plugins/Types/TypeManagerTypeHandler/DynamicBuildManagerTypeManagerTypeHandler/DynamicBuildManagerTypeManagerTypeHandler.cs +++ b/Composite/Plugins/Types/TypeManagerTypeHandler/DynamicBuildManagerTypeManagerTypeHandler/DynamicBuildManagerTypeManagerTypeHandler.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Web.Hosting; using Composite.Core.Types; using Composite.Core.Types.Plugins.TypeManagerTypeHandler; using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; @@ -21,12 +22,17 @@ public Type GetType(string fullName) Type compiledType = CodeGenerationManager.GetCompiledType(fullName); if (compiledType != null) return compiledType; - if (fullName.StartsWith(_prefix) && !fullName.Contains(",")) + string name = fullName; + + bool hasObsoletePrefix = name.StartsWith(_prefix); + if (hasObsoletePrefix) { - string name = fullName.Remove(0, _prefix.Length); - Type resultType = Type.GetType(name + ", Composite.Generated"); + name = name.Substring(_prefix.Length); + } - return resultType; + if (!name.Contains(",") && (hasObsoletePrefix || !HostingEnvironment.IsHosted)) + { + return Type.GetType(name + ", Composite.Generated"); } return null; @@ -40,7 +46,7 @@ public string SerializeType(Type type) { string result; - if (_serializedCache.TryGetValue(type, out result) == false) + if (!_serializedCache.TryGetValue(type, out result)) { result = SerializeTypeImpl(type); _serializedCache.Add(type, result); @@ -62,9 +68,13 @@ public bool HasTypeWithName(string typeFullname) private static string SerializeTypeImpl(Type type) { string assemblyLocation = type.Assembly.Location; + string assemblyCodeBase = type.Assembly.CodeBase; + string tempAssemblyFolderPath = CodeGenerationManager.TempAssemblyFolderPath; + string tempAssemblyFolderUri = new Uri(tempAssemblyFolderPath).AbsoluteUri; - if ((assemblyLocation.StartsWith(CodeGenerationManager.TempAssemblyFolderPath, StringComparison.InvariantCultureIgnoreCase)) || - (assemblyLocation.IndexOf(CodeGenerationManager.CompositeGeneratedFileName, StringComparison.InvariantCultureIgnoreCase) >= 0)) + if (assemblyLocation.StartsWith(tempAssemblyFolderPath, StringComparison.InvariantCultureIgnoreCase) || + assemblyCodeBase.StartsWith(tempAssemblyFolderUri, StringComparison.InvariantCultureIgnoreCase) || + assemblyLocation.IndexOf(CodeGenerationManager.CompositeGeneratedFileName, StringComparison.InvariantCultureIgnoreCase) >= 0) { return type.FullName; } From d4fe2f0dd92625829838519056b9f5683d2c3a64 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 5 Mar 2020 15:41:34 +0100 Subject: [PATCH 31/38] ApplicationStartupAttribute.AbortStartupOnException is now respected when executing "RegisterServices" step --- ...AttributeBasedApplicationStartupHandler.cs | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/Composite/Plugins/Application/ApplicationStartupHandlers/AttributeBasedApplicationStartupHandler/AttributeBasedApplicationStartupHandler.cs b/Composite/Plugins/Application/ApplicationStartupHandlers/AttributeBasedApplicationStartupHandler/AttributeBasedApplicationStartupHandler.cs index 54af94ad7a..0a4148a79f 100644 --- a/Composite/Plugins/Application/ApplicationStartupHandlers/AttributeBasedApplicationStartupHandler/AttributeBasedApplicationStartupHandler.cs +++ b/Composite/Plugins/Application/ApplicationStartupHandlers/AttributeBasedApplicationStartupHandler/AttributeBasedApplicationStartupHandler.cs @@ -94,18 +94,25 @@ public void ConfigureServices(IServiceCollection serviceCollection) { var methodInfo = startupHandler.ConfigureServicesMethod; - if (methodInfo != null) + try { - if (methodInfo.IsStatic) - { - methodInfo.Invoke(null, serviceCollectionParameter); - } - else + if (methodInfo != null) { - var instance = Activator.CreateInstance(methodInfo.DeclaringType); - methodInfo.Invoke(instance, serviceCollectionParameter); + if (methodInfo.IsStatic) + { + methodInfo.Invoke(null, serviceCollectionParameter); + } + else + { + var instance = Activator.CreateInstance(methodInfo.DeclaringType); + methodInfo.Invoke(instance, serviceCollectionParameter); + } } } + catch (Exception ex) + { + ProcessHandlerException(startupHandler, methodInfo, ex); + } } } @@ -493,19 +500,24 @@ private void ExecuteEventHandlers(IServiceProvider serviceProvider, Func Date: Thu, 5 Mar 2020 15:53:26 +0100 Subject: [PATCH 32/38] Fixing some NullReferenceException-s --- Composite/Core/IO/MimeTypeInfo.cs | 5 +++-- .../WidgetFunctionProviderPluginFacade.cs | 21 +++++++------------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Composite/Core/IO/MimeTypeInfo.cs b/Composite/Core/IO/MimeTypeInfo.cs index 7972ad8219..5112919dfa 100644 --- a/Composite/Core/IO/MimeTypeInfo.cs +++ b/Composite/Core/IO/MimeTypeInfo.cs @@ -283,12 +283,13 @@ private static void LoadExtensionMappingsFromWebConfig() return; } - if(config == null) + var configRawXml = config?.SectionInformation.GetRawXml(); + if (configRawXml == null) { return; } - XElement webServerConfig = XElement.Parse(config.SectionInformation.GetRawXml()); + XElement webServerConfig = XElement.Parse(configRawXml); XElement staticContentConfig = webServerConfig.Element("staticContent"); if(staticContentConfig == null) { diff --git a/Composite/Functions/Foundation/PluginFacades/WidgetFunctionProviderPluginFacade.cs b/Composite/Functions/Foundation/PluginFacades/WidgetFunctionProviderPluginFacade.cs index b0a4b0e0fc..c4bd4e6f9e 100644 --- a/Composite/Functions/Foundation/PluginFacades/WidgetFunctionProviderPluginFacade.cs +++ b/Composite/Functions/Foundation/PluginFacades/WidgetFunctionProviderPluginFacade.cs @@ -3,6 +3,7 @@ using System.Configuration; using Composite.Core.Collections.Generic; using Composite.C1Console.Events; +using Composite.Core.Extensions; using Composite.Functions.Plugins.WidgetFunctionProvider; using Composite.Functions.Plugins.WidgetFunctionProvider.Runtime; @@ -26,14 +27,11 @@ public static IEnumerable Functions(string providerName) { using (_resourceLocker.Locker) { - List widgetFunctions = new List(); + var widgetFunctions = new List(); - IWidgetFunctionProvider provider = GetFunctionProvider(providerName); + var provider = GetFunctionProvider(providerName); - foreach (IWidgetFunction widgetFunction in provider.Functions) - { - widgetFunctions.Add(new WidgetFunctionWrapper(widgetFunction)); - } + provider.Functions?.ForEach(func => widgetFunctions.Add(new WidgetFunctionWrapper(func))); return widgetFunctions; } @@ -45,16 +43,13 @@ public static IEnumerable DynamicTypeDependentFunctions(string { using (_resourceLocker.Locker) { - List widgetFunctions = new List(); + var widgetFunctions = new List(); - IDynamicTypeWidgetFunctionProvider provider = GetFunctionProvider(providerName) as IDynamicTypeWidgetFunctionProvider; + var provider = GetFunctionProvider(providerName); - if (provider != null) + if (provider is IDynamicTypeWidgetFunctionProvider dtProvider) { - foreach (IWidgetFunction widgetFunction in provider.DynamicTypeDependentFunctions) - { - widgetFunctions.Add(new WidgetFunctionWrapper(widgetFunction)); - } + dtProvider.DynamicTypeDependentFunctions?.ForEach(func => widgetFunctions.Add(new WidgetFunctionWrapper(func))); } return widgetFunctions; From 76c8d1702b76780e52647d50caf31d3f71e2c3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pauli=20=C3=98ster=C3=B8?= Date: Thu, 5 Mar 2020 16:45:19 +0100 Subject: [PATCH 33/38] Expand NoHttpRazorContext so it supports querying for response cookies and browser capabilities --- Composite/AspNet/Razor/NoHttpRazorContext.cs | 29 ++---- Composite/AspNet/Razor/NoHttpRazorRequest.cs | 98 +++++-------------- Composite/AspNet/Razor/NoHttpRazorResponse.cs | 14 +++ Composite/Composite.csproj | 1 + 4 files changed, 48 insertions(+), 94 deletions(-) create mode 100644 Composite/AspNet/Razor/NoHttpRazorResponse.cs diff --git a/Composite/AspNet/Razor/NoHttpRazorContext.cs b/Composite/AspNet/Razor/NoHttpRazorContext.cs index bcafa6ecb0..c4227e96cf 100644 --- a/Composite/AspNet/Razor/NoHttpRazorContext.cs +++ b/Composite/AspNet/Razor/NoHttpRazorContext.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Web; using System.Web.Instrumentation; @@ -9,27 +9,16 @@ namespace Composite.AspNet.Razor internal class NoHttpRazorContext : HttpContextBase { private readonly IDictionary _items = new Hashtable(); - public override IDictionary Items - { - get { return _items; } - } - - private readonly HttpRequestBase _request = new NoHttpRazorRequest(); - public override HttpRequestBase Request - { - get { return _request; } - } - + private readonly HttpRequestBase _request = new NoHttpRazorRequest(); + private readonly HttpResponseBase _response = new NoHttpRazorResponse(); private readonly PageInstrumentationService _pageInstrumentation = new PageInstrumentationService(); - public override PageInstrumentationService PageInstrumentation - { - get { return _pageInstrumentation; } - } - public override HttpServerUtilityBase Server - { - get { throw new NotSupportedException("Usage of 'Server' isn't supported without HttpContext. Use System.Web.HttpUtility for [html|url] [encoding|decoding]"); } - } + public override IDictionary Items => _items; + public override HttpRequestBase Request => _request; + public override HttpResponseBase Response => _response; + public override PageInstrumentationService PageInstrumentation => _pageInstrumentation; + + public override HttpServerUtilityBase Server => throw new NotSupportedException("Usage of 'Server' isn't supported without HttpContext. Use System.Web.HttpUtility for [html|url] [encoding|decoding]"); public override object GetService(Type serviceType) { diff --git a/Composite/AspNet/Razor/NoHttpRazorRequest.cs b/Composite/AspNet/Razor/NoHttpRazorRequest.cs index 98a1fde73e..a4ed142016 100644 --- a/Composite/AspNet/Razor/NoHttpRazorRequest.cs +++ b/Composite/AspNet/Razor/NoHttpRazorRequest.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Web; using System.Web.Hosting; @@ -13,86 +14,35 @@ internal class NoHttpRazorRequest : HttpRequestBase private NameValueCollection _params; private NameValueCollection _serverVariables; private HttpCookieCollection _cookies; + private HttpBrowserCapabilitiesBase _browser; - public override string ApplicationPath - { - get { return HostingEnvironment.ApplicationVirtualPath; } - } - - public override string PhysicalApplicationPath - { - get { return HostingEnvironment.ApplicationPhysicalPath; } - } - - public override HttpCookieCollection Cookies - { - get { return _cookies ?? (_cookies = new HttpCookieCollection()); } - } - - public override bool IsLocal - { - get { return false; } - } - - public override NameValueCollection Form - { - get { return _form ?? (_form = new NameValueCollection()); } - } - - public override NameValueCollection Headers - { - get { return _headers ?? (_headers = new NameValueCollection());} - } - - public override string HttpMethod - { - get { return "GET"; } - } - - public override bool IsAuthenticated - { - get { return false; } - } - - public override bool IsSecureConnection - { - get { return false; } - } + public override string ApplicationPath => HostingEnvironment.ApplicationVirtualPath; + public override string PhysicalApplicationPath => HostingEnvironment.ApplicationPhysicalPath; - public override string this[string key] + public override HttpBrowserCapabilitiesBase Browser => _browser ?? (_browser = new HttpBrowserCapabilitiesWrapper(new HttpBrowserCapabilities { - get { return null; } - } + Capabilities = new Dictionary() + })); - public override NameValueCollection Params - { - get { return _params ?? (_params = new NameValueCollection()); } - } + public override HttpCookieCollection Cookies => _cookies ?? (_cookies = new HttpCookieCollection()); + public override bool IsLocal => false; + public override NameValueCollection Form => _form ?? (_form = new NameValueCollection()); + public override NameValueCollection Headers => _headers ?? (_headers = new NameValueCollection()); + public override string HttpMethod => "GET"; + public override bool IsAuthenticated => false; + public override bool IsSecureConnection => false; + public override string this[string key] => null; + public override NameValueCollection Params => _params ?? (_params = new NameValueCollection()); + public override string PathInfo => null; + public override NameValueCollection QueryString => _queryString ?? (_queryString = new NameValueCollection()); - public override string PathInfo + public override string RequestType { - get { return null; } + get => HttpMethod; + set => throw new NotSupportedException(); } - public override NameValueCollection QueryString - { - get { return _queryString ?? (_queryString = new NameValueCollection()); } - } - - public override string RequestType - { - get { return HttpMethod; } - set { throw new NotSupportedException();} - } - - public override NameValueCollection ServerVariables - { - get { return _serverVariables ?? (_serverVariables = new NameValueCollection()); } - } - - public override string UserAgent - { - get { return ""; } - } + public override NameValueCollection ServerVariables => _serverVariables ?? (_serverVariables = new NameValueCollection()); + public override string UserAgent => ""; } } diff --git a/Composite/AspNet/Razor/NoHttpRazorResponse.cs b/Composite/AspNet/Razor/NoHttpRazorResponse.cs new file mode 100644 index 0000000000..558464098a --- /dev/null +++ b/Composite/AspNet/Razor/NoHttpRazorResponse.cs @@ -0,0 +1,14 @@ +using System.Collections.Specialized; +using System.Web; + +namespace Composite.AspNet.Razor +{ + internal class NoHttpRazorResponse : HttpResponseBase + { + private NameValueCollection _headers; + private HttpCookieCollection _cookies; + + public override HttpCookieCollection Cookies => _cookies ?? (_cookies = new HttpCookieCollection()); + public override NameValueCollection Headers => _headers ?? (_headers = new NameValueCollection()); + } +} diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index d0bd2ca32c..aac5898d3f 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -216,6 +216,7 @@ + From b15033dd5bd17f7ce884ed6b9f449e3629354fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pauli=20=C3=98ster=C3=B8?= Date: Thu, 5 Mar 2020 16:46:38 +0100 Subject: [PATCH 34/38] Silently return null if the passed key to FindSiteMapNodeFromKey isn't a valid Guid --- Composite/AspNet/CmsPagesSiteMapPlugin.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Composite/AspNet/CmsPagesSiteMapPlugin.cs b/Composite/AspNet/CmsPagesSiteMapPlugin.cs index 23df655360..c2ead63809 100644 --- a/Composite/AspNet/CmsPagesSiteMapPlugin.cs +++ b/Composite/AspNet/CmsPagesSiteMapPlugin.cs @@ -92,10 +92,16 @@ public SiteMapNode FindSiteMapNode(SiteMapProvider provider, string rawUrl) /// public SiteMapNode FindSiteMapNodeFromKey(SiteMapProvider provider, string key) { - var pageId = new Guid(key); - var page = PageManager.GetPageById(pageId); + if (Guid.TryParse(key, out var pageId)) + { + var page = PageManager.GetPageById(pageId); + if (page != null) + { + return new CmsPageSiteMapNode(provider, page); + } + } - return page != null ? new CmsPageSiteMapNode(provider, page) : null; + return null; } /// From fb26003944ffad8dd666fb81c024a625f8c98763 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 13 Mar 2020 16:04:15 +0100 Subject: [PATCH 35/38] Adding [DebuggerDisplay] attribute to generated classes for better debugging experience --- .../DataWrapperCodeGenerator.cs | 55 +++++++++++-------- .../CodeGeneration/EntityClassGenerator.cs | 37 ++++++++++--- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/Composite/Data/Foundation/CodeGeneration/DataWrapperCodeGenerator.cs b/Composite/Data/Foundation/CodeGeneration/DataWrapperCodeGenerator.cs index 68abb66cc7..f160a57592 100644 --- a/Composite/Data/Foundation/CodeGeneration/DataWrapperCodeGenerator.cs +++ b/Composite/Data/Foundation/CodeGeneration/DataWrapperCodeGenerator.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.CodeDom; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Reflection; using Composite.Core.Types; @@ -63,33 +64,35 @@ private static string CreateWrapperClassName(string interfaceTypeFullName) private static CodeTypeDeclaration CreateCodeTypeDeclaration(DataTypeDescriptor dataTypeDescriptor) { - string fullName = dataTypeDescriptor.GetFullInterfaceName(); + string interfaceTypeFullName = dataTypeDescriptor.GetFullInterfaceName(); - IEnumerable> properties = - dataTypeDescriptor.Fields. - Select(f => new Tuple(f.Name, f.InstanceType, f.IsReadOnly)). - Concat(new[] { new Tuple("DataSourceId", typeof(DataSourceId), true) }); - - return CreateCodeTypeDeclaration(fullName, properties); - } + var debugDisplayText = $"Data wrapper for '{interfaceTypeFullName}'"; + foreach (var keyPropertyName in dataTypeDescriptor.KeyPropertyNames) + { + debugDisplayText += $", {keyPropertyName} = {{{keyPropertyName}}}"; + } + var labelFieldName = dataTypeDescriptor.LabelFieldName; + if (!string.IsNullOrEmpty(labelFieldName) && !dataTypeDescriptor.KeyPropertyNames.Contains(labelFieldName)) + { + debugDisplayText += $", {labelFieldName} = {{{labelFieldName}}}"; + } + IEnumerable> properties = + dataTypeDescriptor.Fields. + Select(f => new Tuple(f.Name, f.InstanceType, f.IsReadOnly)). + Concat(new[] { new Tuple("DataSourceId", typeof(DataSourceId), true) }); - /// - /// - /// - /// - /// Tuple(string propertyName, Type propertyType, bool readOnly) - /// - private static CodeTypeDeclaration CreateCodeTypeDeclaration(string interfaceTypeFullName, IEnumerable> properties) - { - CodeTypeDeclaration declaration = new CodeTypeDeclaration(); - declaration.Name = CreateWrapperClassName(interfaceTypeFullName); - declaration.IsClass = true; - declaration.TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed; + var declaration = new CodeTypeDeclaration + { + Name = CreateWrapperClassName(interfaceTypeFullName), + IsClass = true, + TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed + }; declaration.BaseTypes.Add(interfaceTypeFullName); declaration.BaseTypes.Add(typeof(IDataWrapper)); - declaration.CustomAttributes.Add( + declaration.CustomAttributes.AddRange(new[] + { new CodeAttributeDeclaration( new CodeTypeReference(typeof(EditorBrowsableAttribute)), new CodeAttributeArgument( @@ -98,8 +101,14 @@ private static CodeTypeDeclaration CreateCodeTypeDeclaration(string interfaceTyp EditorBrowsableState.Never.ToString() ) ) + ), + new CodeAttributeDeclaration( + new CodeTypeReference(typeof(DebuggerDisplayAttribute)), + new CodeAttributeArgument( + new CodePrimitiveExpression(debugDisplayText) + ) ) - ); + }); declaration.Members.Add(new CodeMemberField(interfaceTypeFullName, WrappedObjectName)); diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityClassGenerator.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityClassGenerator.cs index 0f4a54c71e..067ef9a447 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityClassGenerator.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityClassGenerator.cs @@ -45,24 +45,45 @@ public EntityClassGenerator(DataTypeDescriptor dataTypeDescriptor, string entity public CodeTypeDeclaration CreateClass() { - CodeTypeDeclaration codeTypeDeclaration = new CodeTypeDeclaration(_entityClassName); + var debugDisplayText = $"SQL entity for '{_dataTypeDescriptor.GetFullInterfaceName()}', culture = '{_localeCultureName}', scope = '{_dataScopeIdentifierName}'"; + foreach (var keyPropertyName in _dataTypeDescriptor.KeyPropertyNames) + { + debugDisplayText += $", {keyPropertyName} = {{{keyPropertyName}}}"; + } - codeTypeDeclaration.IsClass = true; - codeTypeDeclaration.TypeAttributes = TypeAttributes.Public; - codeTypeDeclaration.BaseTypes.Add(new CodeTypeReference(_entityBaseClassName)); + var labelFieldName = _dataTypeDescriptor.LabelFieldName; + if (!string.IsNullOrEmpty(labelFieldName) && !_dataTypeDescriptor.KeyPropertyNames.Contains(labelFieldName)) + { + debugDisplayText += $", {labelFieldName} = {{{labelFieldName}}}"; + } + + var codeTypeDeclaration = new CodeTypeDeclaration(_entityClassName) + { + IsClass = true, + TypeAttributes = TypeAttributes.Public + }; - codeTypeDeclaration.CustomAttributes.Add(new CodeAttributeDeclaration( + codeTypeDeclaration.BaseTypes.Add(new CodeTypeReference(_entityBaseClassName)); + codeTypeDeclaration.CustomAttributes.AddRange(new [] + { + new CodeAttributeDeclaration( new CodeTypeReference(typeof(TableAttribute)), new CodeAttributeArgument("Name", new CodePrimitiveExpression(_tableName)) - )); + ), + new CodeAttributeDeclaration( + new CodeTypeReference(typeof(DebuggerDisplayAttribute)), + new CodeAttributeArgument( + new CodePrimitiveExpression(debugDisplayText) + ) + ) + }); - string propertyName = typeof(DataScopeIdentifier).GetProperties(BindingFlags.Static | BindingFlags.Public). Where(f => f.Name.Equals(_dataScopeIdentifierName, StringComparison.OrdinalIgnoreCase)). Select(f => f.Name). - Single(); + Single(); CodeMemberField constDataSourceIdCodeMemberField = new CodeMemberField( From 8593d3fdb6dcfa12fefec9ac4ea7ae059f10bf9d Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 13 Mar 2020 17:16:49 +0100 Subject: [PATCH 36/38] Making sure console log viewer doesn't crash when there are too many log entries --- Composite/Core/Logging/LogManager.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Composite/Core/Logging/LogManager.cs b/Composite/Core/Logging/LogManager.cs index ff404f89ef..1f928c29a8 100644 --- a/Composite/Core/Logging/LogManager.cs +++ b/Composite/Core/Logging/LogManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Composite.Plugins.Logging.LogTraceListeners.FileLogTraceListener; @@ -18,6 +18,8 @@ public static class LogManager /// public static int LogLinesRequestLimit = 5000; + private static int MaxLogEntriesToParse = 50000; + private static LogFileReader[] _logFiles; private static readonly object _syncRoot = new object(); @@ -143,8 +145,24 @@ public static LogEntry[] GetLogEntries(DateTime timeFrom, DateTime timeTo, bool } int entriesRead = 0; + int entriesParsed = 0; foreach (var entry in logFile.GetLogEntries(timeFrom, timeTo)) { + entriesParsed++; + + if (entriesParsed >= MaxLogEntriesToParse) + { + result.Add(new LogEntry + { + ApplicationDomainId = AppDomain.CurrentDomain.Id, + ThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId, + TimeStamp = DateTime.Now, + Title = nameof(LogManager), + Message = $"Maximum amount of parsed log entries reached ({MaxLogEntriesToParse})." + }); + break; + } + if (entry.TimeStamp >= timeFrom && entry.TimeStamp <= timeTo) { if (!includeVerbose && entry.Severity == VerboseSeverity) From d2092c6e29f605aa91f0ec231e02006fc3082337 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 13 Mar 2020 17:50:31 +0100 Subject: [PATCH 37/38] A more description error when there's an attempt to login over HTTP connection, when HTTPS is required for cookies. --- .../HttpContextBasedLoginSessionStore.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Composite/Plugins/Security/LoginSessionStores/HttpContextBasedLoginSessionStore/HttpContextBasedLoginSessionStore.cs b/Composite/Plugins/Security/LoginSessionStores/HttpContextBasedLoginSessionStore/HttpContextBasedLoginSessionStore.cs index bb798f98f5..c7ff204655 100644 --- a/Composite/Plugins/Security/LoginSessionStores/HttpContextBasedLoginSessionStore/HttpContextBasedLoginSessionStore.cs +++ b/Composite/Plugins/Security/LoginSessionStores/HttpContextBasedLoginSessionStore/HttpContextBasedLoginSessionStore.cs @@ -51,9 +51,18 @@ private static void StoreUsernameImpl(string userName, bool persistAcrossSession cookie.HttpOnly = true; var context = HttpContext.Current; - if (context != null && context.Request.IsSecureConnection) + if (context != null) { - cookie.Secure = true; + if (context.Request.IsSecureConnection) + { + cookie.Secure = true; + } + else if (cookie.Secure) + { + throw new InvalidOperationException( + "A login attempt over a not secure connection, when system.web/httpCookies/@requireSSL is set to 'true'. " + + "Either secure connection should be required for console login, or SSL should not be required for cookies."); + } } if (persistAcrossSessions) From a4c2f85e9cedeebf957d2831d9ab00785c954cb7 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 14 Apr 2020 13:22:36 +0200 Subject: [PATCH 38/38] Updating version number to 6.9 --- Composite/Properties/SharedAssemblyInfo.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Composite/Properties/SharedAssemblyInfo.cs b/Composite/Properties/SharedAssemblyInfo.cs index dc4329dde4..b92b89ad53 100644 --- a/Composite/Properties/SharedAssemblyInfo.cs +++ b/Composite/Properties/SharedAssemblyInfo.cs @@ -2,15 +2,15 @@ // General Information about the assemblies Composite and Composite.Workflows #if !InternalBuild -[assembly: AssemblyTitle("C1 CMS 6.8")] +[assembly: AssemblyTitle("C1 CMS 6.9")] #else -[assembly: AssemblyTitle("C1 CMS 6.8 (Internal Build)")] +[assembly: AssemblyTitle("C1 CMS 6.9 (Internal Build)")] #endif -[assembly: AssemblyCompany("Orckestra Inc")] +[assembly: AssemblyCompany("Orckestra Technologies Inc.")] [assembly: AssemblyProduct("C1 CMS")] -[assembly: AssemblyCopyright("Copyright © Orckestra Inc 2019")] +[assembly: AssemblyCopyright("Copyright © Orckestra Technologies Inc. 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("6.8.*")] +[assembly: AssemblyVersion("6.9.*")]