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