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..3c5a9e8bbd --- /dev/null +++ b/Composite/AspNet/Caching/OutputCacheHelper.cs @@ -0,0 +1,195 @@ +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); + + var section = WebConfigurationManager.OpenWebConfiguration(HostingEnvironment.ApplicationVirtualPath) + .GetSection("system.web/caching/outputCacheSettings"); + + if (section is OutputCacheSettingsSection settings) + { + _outputCacheProfiles = settings.OutputCacheProfiles.OfType() + .ToDictionary(_ => _.Name); + } + } + + /// + /// 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 ResponseCacheable(HttpContext context) + { + if (context.Response.StatusCode != 200) + { + return false; + } + + var cacheability = GetPageCacheability(context); + + return cacheability > HttpCacheability.NoCache; + } + + + private static HttpCacheability GetPageCacheability(HttpContext context) + => (HttpCacheability)CacheabilityFieldInfo.GetValue(context.Response.Cache); + + + + public static void InitializeFullPageCaching(HttpContext context) + { + using (var page = new CacheableEmptyPage()) + { + page.ProcessRequest(context); + } + } + + + private class CacheableEmptyPage : 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..56a57043bf --- /dev/null +++ b/Composite/AspNet/CmsPageHttpHandler.cs @@ -0,0 +1,162 @@ +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 templates 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); + + using (var renderingContext = RenderingContext.InitializeFromHttpContext()) + { + bool cachingEnabled = false; + string cacheKey = null; + DonutCacheEntry cacheEntry = null; + + 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); + if (cachingEnabled) + { + using (Profiler.Measure("Cache lookup")) + { + cacheEntry = OutputCacheHelper.GetFromCache(context, cacheKey); + } + } + } + + XDocument document; + var functionContext = PageRenderer.GetPageRenderFunctionContextContainer(); + + 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.ExecuteCacheableFunctions(document.Root, functionContext); + + if (cachingEnabled && !allFunctionsExecuted && OutputCacheHelper.ResponseCacheable(context)) + { + preventResponseCaching = true; + + if (!functionContext.ExceptionsSuppressed) + { + 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); + PageRenderer.ProcessDocumentHead(xhtmlDocument); + + 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(); + } + + // 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) + { + xhtml = renderingContext.BuildProfilerReport(); + + response.ContentType = "text/xml"; + } + + response.Write(xhtml); + } + } + + + public bool IsReusable => true; + } +} \ No newline at end of file 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; } /// 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/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/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..aac5898d3f 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -216,6 +216,7 @@ + @@ -250,20 +251,28 @@ + + + + + + ASPXCodeBehind + + 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/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/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/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/Core/Localization/LocalizationFacade.cs b/Composite/Core/Localization/LocalizationFacade.cs index 97225512fd..aa8cc0af47 100644 --- a/Composite/Core/Localization/LocalizationFacade.cs +++ b/Composite/Core/Localization/LocalizationFacade.cs @@ -373,6 +373,11 @@ public static void RemoveLocale(CultureInfo cultureInfo, bool makeFlush = true) } DynamicTypeManager.RemoveLocale(cultureInfo); + + if (makeFlush) + { + C1Console.Events.GlobalEventSystemFacade.FlushTheSystem(false); + } } } } 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) diff --git a/Composite/Core/PageTemplates/IPageRenderer.cs b/Composite/Core/PageTemplates/IPageRenderer.cs index 4b2f5fb846..c914c7abb0 100644 --- a/Composite/Core/PageTemplates/IPageRenderer.cs +++ b/Composite/Core/PageTemplates/IPageRenderer.cs @@ -1,16 +1,34 @@ -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. + /// 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); + } + + /// + /// 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/PageTemplates/TemplateDefinitionHelper.cs b/Composite/Core/PageTemplates/TemplateDefinitionHelper.cs index 946b95e455..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,14 +125,19 @@ public static void BindPlaceholders(IPageTemplate template, if (functionContextContainer != null) { + bool allFunctionsExecuted; + using (Profiler.Measure($"Evaluating placeholder '{placeholderId}'")) { - PageRenderer.ExecuteEmbeddedFunctions(placeholderXhtml.Root, functionContextContainer); + allFunctionsExecuted = PageRenderer.ExecuteCacheableFunctions(placeholderXhtml.Root, functionContextContainer); } - using (Profiler.Measure("Normalizing XHTML document")) + if (allFunctionsExecuted) { - PageRenderer.NormalizeXhtmlDocument(placeholderXhtml); + using (Profiler.Measure("Normalizing XHTML document")) + { + PageRenderer.NormalizeXhtmlDocument(placeholderXhtml); + } } } @@ -147,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/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/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/Routing/Pages/C1PageRouteHander.cs b/Composite/Core/Routing/Pages/C1PageRouteHander.cs index 949233e213..014fb10211 100644 --- a/Composite/Core/Routing/Pages/C1PageRouteHander.cs +++ b/Composite/Core/Routing/Pages/C1PageRouteHander.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; +using System.Collections.Concurrent; using System.Linq; using System.Web; using System.Web.Compilation; @@ -8,13 +9,20 @@ using System.Web.Routing; 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; + 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; @@ -39,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) { @@ -51,7 +58,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(nameof(C1PageRouteHandler), $"Failed to load type '{typeAttr.Value}'"); } } } @@ -84,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()) { @@ -94,8 +112,17 @@ public IHttpHandler GetHttpHandler(RequestContext requestContext) { return (IHttpHandler)Activator.CreateInstance(_handlerType); } - - return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath("~/Renderers/Page.aspx", typeof(Page)); + + return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(PageHandlerVirtualPath, 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/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/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/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/Page/PageRenderer.cs b/Composite/Core/WebClient/Renderings/Page/PageRenderer.cs index a9f73003a3..7b357f9197 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; @@ -18,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 { @@ -30,6 +32,9 @@ 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"; + private static readonly XName XName_Id = "id"; + private static readonly XName XName_Name = "name"; /// public static FunctionContextContainer GetPageRenderFunctionContextContainer() @@ -54,7 +59,7 @@ public static Control Render(this IPage page, IEnumerable{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()) { @@ -121,15 +132,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; /// @@ -179,11 +170,6 @@ public static IPage CurrentPage { get { - if (!RequestLifetimeCache.HasKey("PageRenderer.IPage")) - { - return null; - } - return RequestLifetimeCache.TryGet("PageRenderer.IPage"); } set @@ -203,20 +189,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; /// @@ -236,27 +209,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")) @@ -269,14 +221,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")) @@ -292,9 +244,92 @@ 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 => + { + using (Profiler.Measure($"Filter: {filter.GetType().FullName}")) + { + filter.Filter(xhtmlDocument, page); + } + }); + } + } + } + + private static bool IsMetaTag(XElement e) => 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) + { + 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); + 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) { - filters.ForEach(_ => _.Filter(xhtmlDocument, page)); + e.Remove(); } } } @@ -305,7 +340,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) { @@ -357,7 +405,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; } @@ -459,7 +507,6 @@ private static void NormalizeAspNetForms(XDocument document) aspNetFormElement.ReplaceWith(aspNetFormElement.Nodes()); } } - } @@ -486,111 +533,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 exception: " + ex.Message)) + { + 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 cacheable (not dynamic) functions and returns True + /// if all of the functions were cacheable. + /// + /// + /// + /// + internal static bool ExecuteCacheableFunctions(XElement element, FunctionContextContainer functionContext) + { + return ExecuteFunctionsRec(element, functionContext, name => + { + var function = FunctionFacade.GetFunction(name); + return !(function is IDynamicFunction df && df.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/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/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/WebClient/Renderings/RenderingContext.cs b/Composite/Core/WebClient/Renderings/RenderingContext.cs index 81b5360349..62fe2135f4 100644 --- a/Composite/Core/WebClient/Renderings/RenderingContext.cs +++ b/Composite/Core/WebClient/Renderings/RenderingContext.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Text; +using System.Threading; using System.Web; using Composite.C1Console.Security; using Composite.Core.Extensions; @@ -53,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; @@ -114,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); } @@ -203,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 { @@ -240,7 +239,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); @@ -293,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 @@ -321,10 +323,7 @@ public void Dispose() if (PreviewMode) { - var cache = HttpRuntime.Cache; - - cache.Remove(_previewKey + "_SelectedPage"); - cache.Remove(_previewKey + "_SelectedContents"); + PagePreviewContext.Remove(_previewKey); } } } 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 = "" } }); 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/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/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; } } } 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/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/Functions/Foundation/PluginFacades/FunctionWrapper.cs b/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs index 6e31908801..7d536e0323 100644 --- a/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs +++ b/Composite/Functions/Foundation/PluginFacades/FunctionWrapper.cs @@ -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,7 @@ bool IFunctionInitializationInfo.FunctionInitializedCorrectly return ((IFunctionInitializationInfo) _functionToWrap).FunctionInitializedCorrectly; } } + + public bool PreventFunctionOutputCaching => _functionToWrap is IDynamicFunction df && df.PreventFunctionOutputCaching; } } 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; 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; } 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); } } } 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/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/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 f.Name.Equals(_dataScopeIdentifierName, StringComparison.OrdinalIgnoreCase)). Select(f => f.Name). - Single(); + Single(); CodeMemberField constDataSourceIdCodeMemberField = new CodeMemberField( 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/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..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()) { @@ -40,8 +46,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 +60,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/StandardFunctionProvider/Pages/GetPageIdFunction.cs b/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs index 3e95ed77af..8ee00a01c8 100644 --- a/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs +++ b/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/Pages/GetPageIdFunction.cs @@ -1,14 +1,17 @@ -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; +using Composite.C1Console.Security; namespace Composite.Plugins.Functions.FunctionProviders.StandardFunctionProvider.Pages { @@ -21,32 +24,95 @@ public GetPageIdFunction(EntityTokenFactory entityTokenFactory) public override object Execute(ParameterList parameters, FunctionContextContainer context) { - SitemapScope SitemapScope; - if (parameters.TryGetParameter("SitemapScope", out SitemapScope) == false) + if (!parameters.TryGetParameter(nameof(SitemapScope), out var sitemapScope)) { - 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 GetPageIdFromEntityToken(entityToken as EntityToken); + } + + private Guid? GetCurrentPageIdFromFormFlow() + { + var currentFormTreeCompiler = FormFlowUiDefinitionRenderer.CurrentFormTreeCompiler; + if (currentFormTreeCompiler.BindingObjects.TryGetValue(FormsWorkflow.EntityTokenKey, out var entityToken)) + { + return GetPageIdFromEntityToken(entityToken as EntityToken); + } + + return null; + } + + private Guid? GetPageIdFromEntityToken(EntityToken entityToken) + { + if (entityToken is null) + { + return null; } - return pageId; + Guid pageId = Guid.Empty; + + 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; } @@ -58,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/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); } 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..c1f3639565 --- /dev/null +++ b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/DataReference/PageReferenceSelectorWidgetFunctionBase.cs @@ -0,0 +1,89 @@ +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 +{ + 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 diff --git a/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/String/TreeSelectorWidgetFunction.cs b/Composite/Plugins/Functions/WidgetFunctionProviders/StandardWidgetFunctionProvider/String/TreeSelectorWidgetFunction.cs index 3438ea107d..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,15 +56,15 @@ 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), 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", + 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); 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/Composite/Plugins/PageTemplates/Razor/RazorPageRenderer.cs b/Composite/Plugins/PageTemplates/Razor/RazorPageRenderer.cs index 8e77fe2e9b..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; @@ -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; @@ -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) 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 + }; + } } } 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) 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; } 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.*")] 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/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" > diff --git a/Website/App_Data/Composite/reset-installation.bat b/Website/App_Data/Composite/reset-installation.bat index 85016c1947..28cfa09399 100644 --- a/Website/App_Data/Composite/reset-installation.bat +++ b/Website/App_Data/Composite/reset-installation.bat @@ -1,4 +1,3 @@ - del DataMetaData\*.xml del DataStores\*.xml del Configuration\DynamicSqlDataProvider.config 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); } /** 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 @@ - + +