From 0cea97b63ef2316078ab4f7190906c6d55734440 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 13 Apr 2016 14:36:55 +0200 Subject: [PATCH 001/213] Making IPage data support multiple versions; Adding IVersioned, ITimedPublishing --- Composite/Composite.csproj | 3 + Composite/Data/DataAttributeFacade.cs | 36 ++++-- Composite/Data/DataIdKeyFacade.cs | 25 ++-- Composite/Data/DataIdKeyFacadeImpl.cs | 5 +- Composite/Data/DataReference.cs | 26 +--- Composite/Data/DataScopeIdentifier.cs | 10 +- .../Data/DynamicTypes/DataTypeDescriptor.cs | 114 ++++++++---------- ...eflectionBasedDataTypeDescriptorBuilder.cs | 76 +++++++----- Composite/Data/ForeignKeyAttribute.cs | 17 ++- .../Data/Foundation/DataReferenceRegistry.cs | 11 +- Composite/Data/IDataExtensions.cs | 2 +- Composite/Data/KeyPropertyNameAttribute.cs | 1 - Composite/Data/Types/IPage.cs | 6 +- .../Data/Types/IPagePlaceholderContent.cs | 10 +- Composite/Data/Types/ITimedPublishing.cs | 29 +++++ Composite/Data/Types/IVersioned.cs | 17 +++ Composite/Data/VersionKeyPropertyName.cs | 15 +++ .../ConsoleCommandHandlers/FocusData.cs | 2 +- .../CodeGeneration/DataIdClassGenerator.cs | 5 +- .../EntityBaseClassGenerator.cs | 90 ++++++++------ .../PageElementProvider.cs | 12 +- .../IDataGenerated/GetXml.cs | 2 +- .../DataInternalUrlConverter.cs | 11 +- 23 files changed, 303 insertions(+), 222 deletions(-) create mode 100644 Composite/Data/Types/ITimedPublishing.cs create mode 100644 Composite/Data/Types/IVersioned.cs create mode 100644 Composite/Data/VersionKeyPropertyName.cs diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 52f02f7d61..710bf09789 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -164,7 +164,10 @@ + + + diff --git a/Composite/Data/DataAttributeFacade.cs b/Composite/Data/DataAttributeFacade.cs index a11ef7580c..b7d3680dd0 100644 --- a/Composite/Data/DataAttributeFacade.cs +++ b/Composite/Data/DataAttributeFacade.cs @@ -6,6 +6,7 @@ using Composite.Core; using Composite.Core.Collections.Generic; using Composite.C1Console.Events; +using Composite.Core.Linq; using Composite.Core.ResourceSystem; using Composite.Core.Types; @@ -359,17 +360,16 @@ public static IReadOnlyList GetDataReferenceProperties(Type { if (attr.InterfaceType == null) { - throw new InvalidOperationException(string.Format("Null argument is not allowed for the attribute '{0}' on the property '{1}'", - typeof(ForeignKeyAttribute), propertyInfo)); + throw new InvalidOperationException( + $"Null argument is not allowed for the attribute '{typeof (ForeignKeyAttribute)}' on the property '{propertyInfo}'"); } if (!typeof (IData).IsAssignableFrom(attr.InterfaceType)) { - throw new InvalidOperationException(string.Format("The argument should inherit the type '{0}' for the attribute '{1}' on the property '{2}'", - typeof(IData), typeof(ForeignKeyAttribute), propertyInfo)); + throw new InvalidOperationException( + $"The argument should inherit the type '{typeof (IData)}' for the attribute '{typeof (ForeignKeyAttribute)}' on the property '{propertyInfo}'"); } - if (attr.IsNullReferenceValueSet) { @@ -421,6 +421,19 @@ orderby kpn.Index } + /// + public static IReadOnlyList GetVersionKeyPropertyNames(this Type interfaceType) + { + Verify.ArgumentNotNull(interfaceType, "interfaceType"); + + var map = _resourceLocker.Resources.InterfaceTypeToVersionKeyPropertyNames; + + return map.GetOrAdd(interfaceType, type => + (from kpn in type.GetCustomAttributesRecursively() + orderby kpn.VersionKeyPropertyName + select kpn.VersionKeyPropertyName).ToList()); + } + /// [Obsolete("Use GetKeyProperties() instead")] public static List GetKeyPropertyInfoes(this IData data) @@ -475,6 +488,12 @@ public static IReadOnlyList GetKeyProperties(this Type interfaceTy }); } + internal static PropertyInfo GetSingleKeyProperty(this Type interfaceType) + { + return interfaceType.GetKeyProperties().SingleOrException( + "No key properties defined on data type '{0}'", + "Multiple key proterties defined for data type '{0}'", interfaceType); + } /// @@ -491,7 +510,7 @@ public static string GetTypeTitle(this IData data) public static string GetTypeTitle(this Type interfaceType) { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - if (typeof(IData).IsAssignableFrom(interfaceType) == false) throw new ArgumentException(string.Format("The specified type must inherit from '{0}", typeof(IData))); + if (!typeof(IData).IsAssignableFrom(interfaceType)) throw new ArgumentException($"The specified type must inherit from '{typeof (IData)}"); string title; @@ -511,7 +530,8 @@ public static string GetTypeTitle(this Type interfaceType) } else { - throw new InvalidOperationException(string.Format("More than one '{0}' defined on the type '{1}'", typeof(TitleAttribute), interfaceType)); + throw new InvalidOperationException( + $"More than one '{typeof (TitleAttribute)}' defined on the type '{interfaceType}'"); } _resourceLocker.Resources.InterfaceTypeToTypeTitle.Add(interfaceType, title); @@ -550,6 +570,7 @@ private sealed class Resources public ConcurrentDictionary> InterfaceTypeToKeyPropertyInfo { get; set; } public Dictionary InterfaceTypeToTypeTitle { get; set; } public ConcurrentDictionary> InterfaceTypeToKeyPropertyNames { get; set; } + public ConcurrentDictionary> InterfaceTypeToVersionKeyPropertyNames { get; set; } public static void Initialize(Resources resources) { @@ -565,6 +586,7 @@ public static void Initialize(Resources resources) resources.InterfaceTypeToKeyPropertyInfo = new ConcurrentDictionary>(); resources.InterfaceTypeToTypeTitle = new Dictionary(); resources.InterfaceTypeToKeyPropertyNames = new ConcurrentDictionary>(); + resources.InterfaceTypeToVersionKeyPropertyNames = new ConcurrentDictionary>(); } } } diff --git a/Composite/Data/DataIdKeyFacade.cs b/Composite/Data/DataIdKeyFacade.cs index c6961d1a31..586174bda9 100644 --- a/Composite/Data/DataIdKeyFacade.cs +++ b/Composite/Data/DataIdKeyFacade.cs @@ -15,7 +15,7 @@ public static class DataIdKeyFacade /// static DataIdKeyFacade() { - GlobalEventSystemFacade.SubscribeToFlushEvent(OnFlushEvent); + GlobalEventSystemFacade.SubscribeToFlushEvent(args => Flush()); } @@ -23,7 +23,12 @@ static DataIdKeyFacade() // Overload /// public static T GetKeyValue(this DataSourceId dataSourceId, string keyName = null) - { + { + if (keyName == null) + { + keyName = dataSourceId.InterfaceType.GetSingleKeyProperty().Name; + } + return (T)_implementation.GetKeyValue(dataSourceId.DataId, keyName); } @@ -41,7 +46,14 @@ public static T GetKeyValue(IDataId dataId, string keyName = null) // Overload /// public static object GetKeyValue(this DataSourceId dataSourceId, string keyName = null) - { + { + Verify.ArgumentNotNull(dataSourceId, nameof(dataSourceId)); + + if (keyName == null) + { + keyName = dataSourceId.InterfaceType.GetSingleKeyProperty().Name; + } + return _implementation.GetKeyValue(dataSourceId.DataId, keyName); } @@ -77,12 +89,5 @@ private static void Flush() { _implementation.OnFlush(); } - - - - private static void OnFlushEvent(FlushEventArgs args) - { - Flush(); - } } } diff --git a/Composite/Data/DataIdKeyFacadeImpl.cs b/Composite/Data/DataIdKeyFacadeImpl.cs index f3f31bb06b..b990a6409b 100644 --- a/Composite/Data/DataIdKeyFacadeImpl.cs +++ b/Composite/Data/DataIdKeyFacadeImpl.cs @@ -21,10 +21,7 @@ public object GetKeyValue(IDataId dataId, string keyName) } - Func valueFactory = f => - { - return f.GetProperty(keyName); - }; + Func valueFactory = f => f.GetProperty(keyName); PropertyInfo keyPropertyInfo = _keyPropertyInfoCache.GetOrAdd(dataId.GetType(), valueFactory); diff --git a/Composite/Data/DataReference.cs b/Composite/Data/DataReference.cs index f647843346..9267b0eba9 100644 --- a/Composite/Data/DataReference.cs +++ b/Composite/Data/DataReference.cs @@ -57,7 +57,7 @@ public DataReference(object keyValue) { if (keyValue != null) { - Type realKeyType = typeof(T).GetKeyProperties().Single().PropertyType; + Type realKeyType = typeof(T).GetSingleKeyProperty().PropertyType; if (keyValue.GetType() != realKeyType) { _keyValue = ValueTypeConverter.Convert(keyValue, realKeyType); @@ -91,13 +91,7 @@ public DataReference(T data) /// /// The type of the data item. This type inherits from IData. /// - public Type ReferencedType - { - get - { - return typeof(T); - } - } + public Type ReferencedType => typeof(T); /// @@ -118,25 +112,13 @@ public bool IsSet /// /// The key value of the data item being referenced, like the Guid for a page id. /// - public object KeyValue - { - get - { - return _keyValue; - } - } + public object KeyValue => _keyValue; /// /// The data item being referenced. /// - IData IDataReference.Data - { - get - { - return this.Data; - } - } + IData IDataReference.Data => this.Data; /// diff --git a/Composite/Data/DataScopeIdentifier.cs b/Composite/Data/DataScopeIdentifier.cs index 3798e93511..607fa0fbd5 100644 --- a/Composite/Data/DataScopeIdentifier.cs +++ b/Composite/Data/DataScopeIdentifier.cs @@ -18,10 +18,10 @@ public sealed class DataScopeIdentifier public const string AdministratedName = "administrated"; /// - public static DataScopeIdentifier Public { get { return new DataScopeIdentifier(PublicName); } } + public static DataScopeIdentifier Public { get; } = new DataScopeIdentifier(PublicName); /// - public static DataScopeIdentifier Administrated { get { return new DataScopeIdentifier(AdministratedName); } } + public static DataScopeIdentifier Administrated { get; } = new DataScopeIdentifier(AdministratedName); /// public static DataScopeIdentifier GetDefault() @@ -54,7 +54,7 @@ public string Serialize() /// public static DataScopeIdentifier Deserialize(string serializedData) { - if (serializedData == null) throw new ArgumentNullException("serializedData"); + Verify.ArgumentNotNull(serializedData, nameof(serializedData)); switch (serializedData) { @@ -95,9 +95,7 @@ public static bool IsLegasyDataScope(string name) /// public override bool Equals(object obj) { - if (obj == null) return false; - - return Equals(obj as DataScopeIdentifier); + return obj != null && Equals(obj as DataScopeIdentifier); } diff --git a/Composite/Data/DynamicTypes/DataTypeDescriptor.cs b/Composite/Data/DynamicTypes/DataTypeDescriptor.cs index 06126c0002..f6efa2ff9c 100644 --- a/Composite/Data/DynamicTypes/DataTypeDescriptor.cs +++ b/Composite/Data/DynamicTypes/DataTypeDescriptor.cs @@ -17,12 +17,14 @@ namespace Composite.Data.DynamicTypes { - /// + /// /// Describes a data type in Composite C1 /// [DebuggerDisplay("Type name = {Namespace + '.' + Name}")] public class DataTypeDescriptor { + private const string LogTitle = nameof(DataTypeDescriptor); + private string _name; private Guid _dataTypeId; private string _namespace; @@ -38,6 +40,7 @@ public DataTypeDescriptor() { this.Fields = new DataFieldDescriptorCollection(this); this.KeyPropertyNames = new DataFieldNameCollection(this.Fields, false, false, false); + this.VersionKeyPropertyNames = new DataFieldNameCollection(this.Fields, false, false, false); this.StoreSortOrderFieldNames = new DataFieldNameCollection(this.Fields, true, false, false); this.IsCodeGenerated = false; this.DataScopes = new List(); @@ -122,6 +125,11 @@ public Guid DataTypeId public DataFieldNameCollection KeyPropertyNames { get; set; } + /// + /// Version keys, appear in the physical order but not included in data references. + /// + public DataFieldNameCollection VersionKeyPropertyNames { get; set; } + /// /// Returns the CLT Type for this data type description. /// @@ -162,8 +170,11 @@ internal IEnumerable KeyFields { get { - return this.KeyPropertyNames.Select(fieldName => this.Fields.Where(field => field.Name == fieldName) - .SingleOrException("Missing a field '{0}'", "Multiple fields with name '{0}'", fieldName)); + Func getField = fieldName => + this.Fields.Where(field => field.Name == fieldName) + .SingleOrException("Missing a field '{0}'", "Multiple fields with name '{0}'", fieldName); + + return this.KeyPropertyNames.Concat(VersionKeyPropertyNames).Select(getField); } } @@ -269,13 +280,7 @@ public bool HasCustomPhysicalSortOrder /// /// When true data can be localized. /// - public bool Localizeable - { - get - { - return SuperInterfaces.Contains(typeof(ILocalizedControlled)); - } - } + public bool Localizeable => SuperInterfaces.Contains(typeof(ILocalizedControlled)); /// @@ -314,7 +319,7 @@ internal void AddSuperInterface(Type interfaceType, bool addInheritedFields) { foreach (PropertyInfo propertyInfo in interfaceType.GetProperties()) { - if (propertyInfo.Name == "PageId" && interfaceType == typeof (IPageData)) + if (propertyInfo.Name == nameof(IPageData.PageId) && interfaceType == typeof (IPageData)) { continue; } @@ -329,19 +334,7 @@ internal void AddSuperInterface(Type interfaceType, bool addInheritedFields) { if (KeyPropertyNames.Contains(propertyName)) continue; - PropertyInfo property = interfaceType.GetProperty(propertyName); - if (property == null) - { - List superInterfaces = interfaceType.GetInterfacesRecursively(t => typeof(IData).IsAssignableFrom(t) && t != typeof(IData)); - - foreach (Type superInterface in superInterfaces) - { - property = superInterface.GetProperty(propertyName); - if (property != null) break; - } - } - - Verify.IsNotNull(property, "Missing property '{0}' on type '{1}' or one of its interfaces".FormatWith(propertyName, interfaceType)); + PropertyInfo property = ReflectionBasedDescriptorBuilder.FindProperty(interfaceType, propertyName); if (DynamicTypeReflectionFacade.IsKeyField(property)) { @@ -349,7 +342,16 @@ internal void AddSuperInterface(Type interfaceType, bool addInheritedFields) } } - foreach (DataScopeIdentifier dataScopeIdentifier in DynamicTypeReflectionFacade.GetDataScopes(interfaceType)) + foreach (string propertyName in interfaceType.GetVersionKeyPropertyNames()) + { + if (VersionKeyPropertyNames.Contains(propertyName)) continue; + + ReflectionBasedDescriptorBuilder.FindProperty(interfaceType, propertyName); + + this.VersionKeyPropertyNames.Add(propertyName, false); + } + + foreach (var dataScopeIdentifier in DynamicTypeReflectionFacade.GetDataScopes(interfaceType)) { if (!this.DataScopes.Contains(dataScopeIdentifier)) { @@ -357,8 +359,8 @@ internal void AddSuperInterface(Type interfaceType, bool addInheritedFields) } } - - foreach (Type superSuperInterfaceType in interfaceType.GetInterfaces().Where(t => typeof(IData).IsAssignableFrom(t))) + var superInterfaces = interfaceType.GetInterfaces().Where(t => typeof (IData).IsAssignableFrom(t)); + foreach (Type superSuperInterfaceType in superInterfaces) { AddSuperInterface(superSuperInterfaceType, addInheritedFields); } @@ -366,6 +368,8 @@ internal void AddSuperInterface(Type interfaceType, bool addInheritedFields) + + /// /// Removes a super interface /// @@ -384,7 +388,7 @@ public void RemoveSuperInterface(Type interfaceType) foreach (PropertyInfo propertyInfo in interfaceType.GetProperties()) { - DataFieldDescriptor dataFieldDescriptor = ReflectionBasedDescriptorBuilder.BuildFieldDescriptor(propertyInfo, true); + var dataFieldDescriptor = ReflectionBasedDescriptorBuilder.BuildFieldDescriptor(propertyInfo, true); if (this.Fields.Contains(dataFieldDescriptor)) { @@ -399,7 +403,7 @@ public void RemoveSuperInterface(Type interfaceType) } - foreach (DataScopeIdentifier dataScopeIdentifier in DynamicTypeReflectionFacade.GetDataScopes(interfaceType)) + foreach (var dataScopeIdentifier in DynamicTypeReflectionFacade.GetDataScopes(interfaceType)) { if (this.DataScopes.Contains(dataScopeIdentifier)) { @@ -407,10 +411,10 @@ public void RemoveSuperInterface(Type interfaceType) } } - - foreach (Type superSuperInterfaceType in interfaceType.GetInterfaces().Where(t => typeof(IData).IsAssignableFrom(t))) + var superInterfaces = interfaceType.GetInterfaces().Where(t => typeof (IData).IsAssignableFrom(t)); + foreach (Type superInterfaceType in superInterfaces) { - RemoveSuperInterface(superSuperInterfaceType); + RemoveSuperInterface(superInterfaceType); } } @@ -419,14 +423,7 @@ public void RemoveSuperInterface(Type interfaceType) /// /// All interfaces this data type inherit from /// - public IEnumerable SuperInterfaces - { - get - { - return _superInterfaces; - } - } - + public IEnumerable SuperInterfaces => _superInterfaces; /// @@ -467,8 +464,8 @@ public bool IsPageMetaDataType { get { - return - this.DataAssociations.Any(f => f.AssociatedInterfaceType == typeof(IPage) && f.AssociationType == DataAssociationType.Composition); + return this.DataAssociations.Any(f => f.AssociatedInterfaceType == typeof(IPage) + && f.AssociationType == DataAssociationType.Composition); } } @@ -499,13 +496,13 @@ public void Validate() if (this.DataScopes.Count == 0) throw new InvalidOperationException("The DataScopes list containing the list of data scopes this type must support can not be empty. Please provide at least one data scopes."); if (this.DataScopes.Select(f => f.Name).Distinct().Count() != this.DataScopes.Count) throw new InvalidOperationException("The DataScopes list contains redundant data scopes"); - if (this.DataScopes.Any(f => f.Equals(DataScopeIdentifier.PublicName))) + if (this.DataScopes.Any(f => f.Equals(DataScopeIdentifier.Public))) { foreach (PropertyInfo propertyInfo in typeof(IPublishControlled).GetProperties()) { if (!this.Fields.Any(f => f.Name == propertyInfo.Name)) { - throw new InvalidOperationException(string.Format("DataScope '{0}' require you to implement '{1}' and a field named '{2} is missing", DataScopeIdentifier.Public, typeof(IPublishControlled), propertyInfo.Name)); + throw new InvalidOperationException($"DataScope '{DataScopeIdentifier.Public}' require you to implement '{typeof (IPublishControlled)}' and a field named '{propertyInfo.Name} is missing"); } } } @@ -517,7 +514,7 @@ public void Validate() { if (!this.Fields.Any(f => f.Name == this.LabelFieldName)) { - throw new InvalidOperationException(string.Format("The label field name '{0}' is not an existing field", this.LabelFieldName)); + throw new InvalidOperationException($"The label field name '{this.LabelFieldName}' is not an existing field"); } } @@ -541,8 +538,8 @@ public void Validate() } catch (Exception ex) { - string typeName = (string.IsNullOrEmpty(this.TypeManagerTypeName) ? this.Name : this.TypeManagerTypeName); - throw new InvalidOperationException(string.Format("Failed to validate data type description for '{0}'. {1}", typeName, ex.Message)); + string typeName = string.IsNullOrEmpty(this.TypeManagerTypeName) ? this.Name : this.TypeManagerTypeName; + throw new InvalidOperationException($"Failed to validate data type description for '{typeName}'.", ex); } } @@ -609,7 +606,6 @@ public XElement ToXml() new XAttribute("name", this.Name), new XAttribute("namespace", this.Namespace), this.Title != null ? new XAttribute("title", this.Title) : null, - new XAttribute("hasCustomPhysicalSortOrder", this.HasCustomPhysicalSortOrder), new XAttribute("isCodeGenerated", this.IsCodeGenerated), new XAttribute("cachable", this.Cachable), this.LabelFieldName != null ? new XAttribute("labelFieldName", this.LabelFieldName) : null, @@ -626,6 +622,10 @@ public XElement ToXml() DataScopes.Select(dsi => new XElement("DataScopeIdentifier", new XAttribute("name", dsi)))), new XElement("KeyPropertyNames", KeyPropertyNames.Select(name => new XElement("KeyPropertyName", new XAttribute("name", name)))), + VersionKeyPropertyNames.Any() + ? new XElement("VersionKeyPropertyNames", + VersionKeyPropertyNames.Select(name => new XElement("VersionKeyPropertyName", new XAttribute("name", name)))) + : null, new XElement("SuperInterfaces", SuperInterfaces.Select(su => new XElement("SuperInterface", new XAttribute("type", TypeManager.SerializeType(su))))), new XElement("Fields", Fields.Select(f => f.ToXml())) @@ -660,9 +660,6 @@ internal static DataTypeDescriptor FromXml(XElement element, bool inheritedField string name = element.GetRequiredAttributeValue("name"); string @namespace = element.GetRequiredAttributeValue("namespace"); - // TODO: check why "hasCustomPhysicalSortOrder" is not used - bool hasCustomPhysicalSortOrder = (bool) element.GetRequiredAttribute("hasCustomPhysicalSortOrder"); - bool isCodeGenerated = (bool) element.GetRequiredAttribute("isCodeGenerated"); XAttribute cachableAttribute = element.Attribute("cachable"); XAttribute buildNewHandlerTypeNameAttribute = element.Attribute("buildNewHandlerTypeName"); @@ -710,7 +707,7 @@ internal static DataTypeDescriptor FromXml(XElement element, bool inheritedField string dataScopeName = elm.GetRequiredAttributeValue("name"); if (DataScopeIdentifier.IsLegasyDataScope(dataScopeName)) { - Log.LogWarning("DataTypeDescriptor", "Ignored legacy data scope '{0}' on type '{1}.{2}' while deserializing DataTypeDescriptor. The '{0}' data scope is no longer supported.".FormatWith(dataScopeName, @namespace, name)); + Log.LogWarning(LogTitle, "Ignored legacy data scope '{0}' on type '{1}.{2}' while deserializing DataTypeDescriptor. The '{0}' data scope is no longer supported.".FormatWith(dataScopeName, @namespace, name)); continue; } @@ -725,7 +722,7 @@ internal static DataTypeDescriptor FromXml(XElement element, bool inheritedField if (superInterfaceTypeName.StartsWith("Composite.Data.ProcessControlled.IDeleteControlled")) { - Log.LogWarning("DataTypeDescriptor", string.Format("Ignored legacy super interface '{0}' on type '{1}.{2}' while deserializing DataTypeDescriptor. This super interface is no longer supported.", superInterfaceTypeName, @namespace, name)); + Log.LogWarning(LogTitle, $"Ignored legacy super interface '{superInterfaceTypeName}' on type '{@namespace}.{name}' while deserializing DataTypeDescriptor. This super interface is no longer supported."); continue; } @@ -737,7 +734,7 @@ internal static DataTypeDescriptor FromXml(XElement element, bool inheritedField } catch (Exception ex) { - throw XmlConfigurationExtensionMethods.GetConfigurationException("Failed to load super interface '{0}'".FormatWith(superInterfaceTypeName), ex, elm); + throw XmlConfigurationExtensionMethods.GetConfigurationException($"Failed to load super interface '{superInterfaceTypeName}'", ex, elm); } dataTypeDescriptor.AddSuperInterface(superInterface, !inheritedFieldsIncluded); @@ -818,13 +815,10 @@ public override bool Equals(object obj) /// public bool Equals(DataTypeDescriptor dataTypeDescriptor) { - if (dataTypeDescriptor == null) return false; - - return dataTypeDescriptor.DataTypeId == this.DataTypeId; + return dataTypeDescriptor != null && dataTypeDescriptor.DataTypeId == this.DataTypeId; } - /// public override string ToString() { @@ -868,9 +862,7 @@ public static bool ValidateRuntimeType(this DataTypeDescriptor dataTypeDescripto if (dataTypeDescriptor.IsCodeGenerated) return true; Type dataType = TypeManager.TryGetType(dataTypeDescriptor.TypeManagerTypeName); - if (dataType == null) return false; - - return true; + return dataType != null; } } } diff --git a/Composite/Data/DynamicTypes/Foundation/ReflectionBasedDataTypeDescriptorBuilder.cs b/Composite/Data/DynamicTypes/Foundation/ReflectionBasedDataTypeDescriptorBuilder.cs index 7d48512751..0140148515 100644 --- a/Composite/Data/DynamicTypes/Foundation/ReflectionBasedDataTypeDescriptorBuilder.cs +++ b/Composite/Data/DynamicTypes/Foundation/ReflectionBasedDataTypeDescriptorBuilder.cs @@ -50,7 +50,7 @@ public static DataTypeDescriptor Build(Type type) { foreach (PropertyInfo propertyInfo in superInterfaceType.GetProperties()) { - if (propertyInfo.Name == "PageId" && propertyInfo.DeclaringType == typeof(IPageData)) + if (propertyInfo.Name == nameof(IPageData.PageId) && propertyInfo.DeclaringType == typeof(IPageData)) { continue; } @@ -61,7 +61,7 @@ public static DataTypeDescriptor Build(Type type) } } - ValidateAndAddKeyProperties(typeDescriptor.KeyPropertyNames, type, superInterfaces); + ValidateAndAddKeyProperties(typeDescriptor.KeyPropertyNames, typeDescriptor.VersionKeyPropertyNames, type); string[] storeSortOrder = DynamicTypeReflectionFacade.GetSortOrder(type); if (storeSortOrder != null) @@ -74,7 +74,7 @@ public static DataTypeDescriptor Build(Type type) CheckSortOrder(typeDescriptor); - foreach (DataScopeIdentifier dataScopeIdentifier in DynamicTypeReflectionFacade.GetDataScopes(type)) + foreach (var dataScopeIdentifier in DynamicTypeReflectionFacade.GetDataScopes(type)) { if (!typeDescriptor.DataScopes.Contains(dataScopeIdentifier)) { @@ -86,7 +86,8 @@ public static DataTypeDescriptor Build(Type type) { if (typeDescriptor.Fields[keyPropertyName] == null) { - throw new InvalidOperationException(string.Format("The type '{0}' has a non existing key property specified by the attribute '{1}'", type, typeof(KeyPropertyNameAttribute))); + throw new InvalidOperationException( + $"The type '{type}' has a non existing key property specified by the attribute '{typeof (KeyPropertyNameAttribute)}'"); } } @@ -97,7 +98,7 @@ public static DataTypeDescriptor Build(Type type) { if (typeDescriptor.Fields[field.Item1] == null) { - throw new InvalidOperationException(string.Format("Index field '{0}' is not defined", field.Item1)); + throw new InvalidOperationException($"Index field '{field.Item1}' is not defined"); } } @@ -111,30 +112,49 @@ public static DataTypeDescriptor Build(Type type) return typeDescriptor; } - static void ValidateAndAddKeyProperties(DataFieldNameCollection keyProperties, Type interfaceType, IList superInterfacesType) + static void ValidateAndAddKeyProperties( + DataFieldNameCollection keyProperties, + DataFieldNameCollection versionKeyProperties, + Type interfaceType) { - foreach (string propertyName in DataAttributeFacade.GetKeyPropertyNames(interfaceType)) + foreach (string propertyName in interfaceType.GetKeyPropertyNames()) { - PropertyInfo property = interfaceType.GetProperty(propertyName); - if (property == null) - { - foreach (Type superInterface in superInterfacesType) - { - property = superInterface.GetProperty(propertyName); - if(property != null) break; - } - } - - Verify.IsNotNull(property, "Missing property '{0}' on type '{1}' or one of its interfaces".FormatWith(propertyName, interfaceType)); + PropertyInfo property = FindProperty(interfaceType, propertyName); if (DynamicTypeReflectionFacade.IsKeyField(property)) { keyProperties.Add(propertyName, false); } } + + foreach (string propertyName in interfaceType.GetVersionKeyPropertyNames()) + { + FindProperty(interfaceType, propertyName); + + versionKeyProperties.Add(propertyName, false); + } } + internal static PropertyInfo FindProperty(Type interfaceType, string propertyName) + { + PropertyInfo property = interfaceType.GetProperty(propertyName); + if (property == null) + { + List superInterfaces = interfaceType.GetInterfacesRecursively(t => typeof(IData).IsAssignableFrom(t) && t != typeof(IData)); + + foreach (Type superInterface in superInterfaces) + { + property = superInterface.GetProperty(propertyName); + if (property != null) break; + } + } + + Verify.IsNotNull(property, $"Missing property '{propertyName}' on type '{interfaceType}' or one of its interfaces"); + + return property; + } + internal static DataFieldDescriptor BuildFieldDescriptor(PropertyInfo propertyInfo, bool inherited) { string fieldName = propertyInfo.Name; @@ -150,7 +170,9 @@ internal static DataFieldDescriptor BuildFieldDescriptor(PropertyInfo propertyIn IsNullable = DynamicTypeReflectionFacade.IsNullable(propertyInfo), ForeignKeyReferenceTypeName = DynamicTypeReflectionFacade.ForeignKeyReferenceTypeName(propertyInfo), GroupByPriority = DynamicTypeReflectionFacade.GetGroupByPriority(propertyInfo), - TreeOrderingProfile = DynamicTypeReflectionFacade.GetTreeOrderingProfile(propertyInfo) + TreeOrderingProfile = DynamicTypeReflectionFacade.GetTreeOrderingProfile(propertyInfo), + NewInstanceDefaultFieldValue = DynamicTypeReflectionFacade.NewInstanceDefaultFieldValue(propertyInfo), + IsReadOnly = !propertyInfo.CanWrite }; var formRenderingProfile = DynamicTypeReflectionFacade.GetFormRenderingProfile(propertyInfo); @@ -173,18 +195,8 @@ internal static DataFieldDescriptor BuildFieldDescriptor(PropertyInfo propertyIn //} int position; - if (DynamicTypeReflectionFacade.TryGetFieldPosition(propertyInfo, out position)) - { - fieldDescriptor.Position = position; - } - else - { - fieldDescriptor.Position = 1000; - } - - fieldDescriptor.NewInstanceDefaultFieldValue = DynamicTypeReflectionFacade.NewInstanceDefaultFieldValue(propertyInfo); - - fieldDescriptor.IsReadOnly = !propertyInfo.CanWrite; + fieldDescriptor.Position = DynamicTypeReflectionFacade.TryGetFieldPosition(propertyInfo, out position) + ? position : 1000; return fieldDescriptor; } @@ -225,7 +237,7 @@ private static void CheckSortOrder(DataTypeDescriptor typeDescriptor) if (typeDescriptor.StoreSortOrderFieldNames.Count != typeDescriptor.Fields.Count) { - throw new InvalidOperationException(string.Format("The store sort order attribute should list all the fields of the interface")); + throw new InvalidOperationException("The store sort order attribute should list all the fields of the interface"); } } } diff --git a/Composite/Data/ForeignKeyAttribute.cs b/Composite/Data/ForeignKeyAttribute.cs index 2572887b50..be0799f112 100644 --- a/Composite/Data/ForeignKeyAttribute.cs +++ b/Composite/Data/ForeignKeyAttribute.cs @@ -1,7 +1,5 @@ using System; -using System.Linq; using Composite.Core.Types; -using Composite.Data.DynamicTypes; namespace Composite.Data @@ -112,11 +110,7 @@ public bool NullableString /// - public bool IsNullReferenceValueSet - { - get { return _isNullReferenceValueSet; } - } - + public bool IsNullReferenceValueSet => _isNullReferenceValueSet; /// @@ -181,11 +175,14 @@ public string KeyPropertyName { get { - lock (_lock) + if (_keyPropertyName == null) { - if (_keyPropertyName == null) + lock (_lock) { - _keyPropertyName = DynamicTypeManager.GetDataTypeDescriptor(this.InterfaceType).KeyPropertyNames.Single(); + if (_keyPropertyName == null) + { + _keyPropertyName = InterfaceType.GetSingleKeyProperty().Name; + } } } diff --git a/Composite/Data/Foundation/DataReferenceRegistry.cs b/Composite/Data/Foundation/DataReferenceRegistry.cs index 8786c10f95..a4b2f65139 100644 --- a/Composite/Data/Foundation/DataReferenceRegistry.cs +++ b/Composite/Data/Foundation/DataReferenceRegistry.cs @@ -95,8 +95,10 @@ private static void AddNewType(Type interfaceType) foreach (ForeignPropertyInfo foreignKeyPropertyInfo in foreignKeyProperties) { - if (foreignKeyPropertyInfo.SourcePropertyInfo.CanRead == false) throw new InvalidOperationException(string.Format("The property '{0}' shoud have a getter", foreignKeyPropertyInfo.SourcePropertyInfo)); - if (foreignKeyPropertyInfo.TargetType.IsNotReferenceable()) throw new InvalidOperationException(string.Format("The referenced type '{0}' is marked NotReferenceable and can not be referenced by the interfaceType '{1}'", foreignKeyPropertyInfo.TargetType, interfaceType)); + if (!foreignKeyPropertyInfo.SourcePropertyInfo.CanRead) throw new InvalidOperationException( + $"The property '{foreignKeyPropertyInfo.SourcePropertyInfo}' shoud have a getter"); + if (foreignKeyPropertyInfo.TargetType.IsNotReferenceable()) throw new InvalidOperationException( + $"The referenced type '{foreignKeyPropertyInfo.TargetType}' is marked NotReferenceable and can not be referenced by the interfaceType '{interfaceType}'"); PropertyInfo propertyInfo = foreignKeyPropertyInfo.TargetType.GetDataPropertyRecursively(foreignKeyPropertyInfo.TargetKeyPropertyName); @@ -112,7 +114,8 @@ private static void AddNewType(Type interfaceType) sourcePropertyType = sourcePropertyType.GetGenericArguments()[0]; } - if (propertyInfo.PropertyType != sourcePropertyType) throw new InvalidOperationException(string.Format("Type mismatch '{0}' and '{1}' does not match from the two properties '{2}' and '{3}'", propertyInfo.PropertyType, foreignKeyPropertyInfo.SourcePropertyInfo.PropertyType, propertyInfo, foreignKeyPropertyInfo.SourcePropertyInfo)); + if (propertyInfo.PropertyType != sourcePropertyType) throw new InvalidOperationException( + $"Type mismatch '{propertyInfo.PropertyType}' and '{foreignKeyPropertyInfo.SourcePropertyInfo.PropertyType}' does not match from the two properties '{propertyInfo}' and '{foreignKeyPropertyInfo.SourcePropertyInfo}'"); foreignKeyPropertyInfo.TargetKeyPropertyInfo = propertyInfo; } @@ -142,7 +145,7 @@ private static void AddNewType(Type interfaceType) if (!DataProviderRegistry.AllInterfaces.Contains(foreignKeyPropertyInfo.TargetType)) { - Log.LogCritical(LogTitle, string.Format("The one type '{0}' is referring the non supported data type '{1}'", interfaceType, foreignKeyPropertyInfo.TargetType)); + Log.LogCritical(LogTitle, $"The one type '{interfaceType}' is referring the non supported data type '{foreignKeyPropertyInfo.TargetType}'"); } } } diff --git a/Composite/Data/IDataExtensions.cs b/Composite/Data/IDataExtensions.cs index 9c704e53da..7344f8e601 100644 --- a/Composite/Data/IDataExtensions.cs +++ b/Composite/Data/IDataExtensions.cs @@ -187,7 +187,7 @@ public static object GetUniqueKey(this IData data) { Verify.ArgumentNotNull(data, "data"); - return data.DataSourceId.InterfaceType.GetKeyProperties().Single().GetValue(data, null); + return data.DataSourceId.InterfaceType.GetSingleKeyProperty().GetValue(data, null); } diff --git a/Composite/Data/KeyPropertyNameAttribute.cs b/Composite/Data/KeyPropertyNameAttribute.cs index aa81d3a6ec..3e01be2342 100644 --- a/Composite/Data/KeyPropertyNameAttribute.cs +++ b/Composite/Data/KeyPropertyNameAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Data.SqlClient; namespace Composite.Data { diff --git a/Composite/Data/Types/IPage.cs b/Composite/Data/Types/IPage.cs index 84cf25a0c2..8b9fa49352 100644 --- a/Composite/Data/Types/IPage.cs +++ b/Composite/Data/Types/IPage.cs @@ -18,17 +18,17 @@ namespace Composite.Data.Types [Title("C1 Page")] [AutoUpdateble] [ImmutableTypeId("{C046F704-D3E4-4b3d-8CB9-77564FB0B9E7}")] - [KeyPropertyName("Id")] + [KeyPropertyName(nameof(Id))] [DataAncestorProvider(typeof(PageDataAncestorProvider))] [DataScope(DataScopeIdentifier.PublicName)] [DataScope(DataScopeIdentifier.AdministratedName)] - [LabelPropertyName("Title")] + [LabelPropertyName(nameof(Title))] [RelevantToUserType(UserType.Developer)] [CachingAttribute(CachingType.Full)] [PublishControlledAuxiliary(typeof(PagePublishControlledAuxiliary))] [PublishProcessControllerTypeAttribute(typeof(GenericPublishProcessController))] [KeyTemplatedXhtmlRenderer(XhtmlRenderingType.Embedable, "{label}")] - public interface IPage : IData, IChangeHistory, IPublishControlled, ILocalizedControlled + public interface IPage : IData, IChangeHistory, IPublishControlled, ILocalizedControlled, ITimedPublishing { /// [StoreFieldType(PhysicalStoreFieldType.Guid)] diff --git a/Composite/Data/Types/IPagePlaceholderContent.cs b/Composite/Data/Types/IPagePlaceholderContent.cs index 081110718b..b86259e032 100644 --- a/Composite/Data/Types/IPagePlaceholderContent.cs +++ b/Composite/Data/Types/IPagePlaceholderContent.cs @@ -14,10 +14,10 @@ namespace Composite.Data.Types [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [AutoUpdateble] [ImmutableTypeId("{3EAA3814-04E6-4c7f-8F1A-004A89BB0848}")] - [KeyPropertyName(0, "PageId")] - [KeyPropertyName(1, "PlaceHolderId")] + [KeyPropertyName(0, nameof(PageId))] + [KeyPropertyName(1, nameof(PlaceHolderId))] [DataAncestorProvider(typeof(PropertyDataAncestorProvider))] - [PropertyDataAncestorProvider("PageId", typeof(IPage), "Id", null)] + [PropertyDataAncestorProvider(nameof(PageId), typeof(IPage), nameof(IPage.Id), null)] [DataScope(DataScopeIdentifier.PublicName)] [DataScope(DataScopeIdentifier.AdministratedName)] [CachingAttribute(CachingType.Full)] @@ -28,14 +28,14 @@ public interface IPagePlaceholderContent : IData, IChangeHistory, IPublishContro /// [StoreFieldType(PhysicalStoreFieldType.Guid)] [ImmutableFieldId("{19DFF302-F089-4900-8B64-35F88C82EC45}")] - [ForeignKey(typeof(IPage), "Id", AllowCascadeDeletes = true)] + [ForeignKey(typeof(IPage), nameof(IPage.Id), AllowCascadeDeletes = true)] Guid PageId { get; set; } /// [StoreFieldType(PhysicalStoreFieldType.String, 255)] [ImmutableFieldId("{D8243AA6-A02A-4383-9ED1-2A7C1A8841E2}")] - [NotNullValidator()] + [NotNullValidator] string PlaceHolderId { get; set; } diff --git a/Composite/Data/Types/ITimedPublishing.cs b/Composite/Data/Types/ITimedPublishing.cs new file mode 100644 index 0000000000..c3bf7446aa --- /dev/null +++ b/Composite/Data/Types/ITimedPublishing.cs @@ -0,0 +1,29 @@ +using System; + +namespace Composite.Data.Types +{ + /// + /// Represents a data item with timed publishing + /// + public interface ITimedPublishing: IVersioned, IData + { + /// + /// Time from which the current version of the data item will be visible + /// + [StoreFieldType(PhysicalStoreFieldType.DateTime, IsNullable = true)] + [ImmutableFieldId("{d70973a4-3b57-49b4-81ed-34ae92d8637f}")] + DateTime? PublishTime { get; set; } + + /// + /// Time until which the current version of the data item will be visible + /// + [StoreFieldType(PhysicalStoreFieldType.DateTime, IsNullable = true)] + [ImmutableFieldId("{806d1edc-b3f2-4cb4-b18f-199cca9ee2df}")] + DateTime? UnpublishTime { get; set; } + + /// + [StoreFieldType(PhysicalStoreFieldType.String, 128, IsNullable = true)] + [ImmutableFieldId("{70b53ac6-d58e-4624-ab47-975e80853673}")] + string VersionTag { get; set; } + } +} diff --git a/Composite/Data/Types/IVersioned.cs b/Composite/Data/Types/IVersioned.cs new file mode 100644 index 0000000000..abb9c42d2e --- /dev/null +++ b/Composite/Data/Types/IVersioned.cs @@ -0,0 +1,17 @@ +using System; + +namespace Composite.Data.Types +{ + /// + /// Represends a data type that supports multiple versions of the same data type + /// + [VersionKeyPropertyName(nameof(VersionId))] + public interface IVersioned : IData + { + /// + [StoreFieldType(PhysicalStoreFieldType.Guid)] + [ImmutableFieldId("{f42d511b-1d76-4c05-a895-99f23b757e1e}")] + [DefaultFieldNewGuidValue] + Guid VersionId { get; } + } +} diff --git a/Composite/Data/VersionKeyPropertyName.cs b/Composite/Data/VersionKeyPropertyName.cs new file mode 100644 index 0000000000..1471511508 --- /dev/null +++ b/Composite/Data/VersionKeyPropertyName.cs @@ -0,0 +1,15 @@ +using System; + +namespace Composite.Data +{ + [AttributeUsage(AttributeTargets.Interface, AllowMultiple = true, Inherited = true)] + public sealed class VersionKeyPropertyNameAttribute : Attribute + { + public VersionKeyPropertyNameAttribute(string propertyName) + { + this.VersionKeyPropertyName = propertyName; + } + + public string VersionKeyPropertyName { get; } + } +} diff --git a/Composite/Plugins/Commands/ConsoleCommandHandlers/FocusData.cs b/Composite/Plugins/Commands/ConsoleCommandHandlers/FocusData.cs index 02f0ad4999..09000fa066 100644 --- a/Composite/Plugins/Commands/ConsoleCommandHandlers/FocusData.cs +++ b/Composite/Plugins/Commands/ConsoleCommandHandlers/FocusData.cs @@ -29,7 +29,7 @@ public void HandleConsoleCommand(string consoleId, string commandPayload) return; } - var keyProperty = type.GetKeyProperties().Single(); + var keyProperty = type.GetSingleKeyProperty(); object key = ValueTypeConverter.Convert(keyString, keyProperty.PropertyType); diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/DataIdClassGenerator.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/DataIdClassGenerator.cs index ee23566b44..af252e1604 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/DataIdClassGenerator.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/DataIdClassGenerator.cs @@ -67,9 +67,10 @@ private void AddConstructor(CodeTypeDeclaration declaration) private void AddProperties(CodeTypeDeclaration declaration) { - foreach (string keyPropertyName in _dataTypeDescriptor.KeyPropertyNames) + foreach (var keyProperty in _dataTypeDescriptor.KeyFields) { - Type keyPropertyType = _dataTypeDescriptor.Fields[keyPropertyName].InstanceType; + string keyPropertyName = keyProperty.Name; + Type keyPropertyType = keyProperty.InstanceType; string propertyFieldName = MakePropertyFieldName(keyPropertyName); declaration.Members.Add(new CodeMemberField(keyPropertyType, propertyFieldName)); diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityBaseClassGenerator.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityBaseClassGenerator.cs index 7562ba9ee2..1d2fce0c4f 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityBaseClassGenerator.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityBaseClassGenerator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.Linq; using System.Reflection; using Composite.Core.Types; using Composite.Data; @@ -29,10 +30,12 @@ public EntityBaseClassGenerator(DataTypeDescriptor dataTypeDescriptor, string en public CodeTypeDeclaration CreateClass() { - CodeTypeDeclaration codeTypeDeclaration = new CodeTypeDeclaration(_entityBaseClassName); + var codeTypeDeclaration = new CodeTypeDeclaration(_entityBaseClassName) + { + IsClass = true, + TypeAttributes = TypeAttributes.Public | TypeAttributes.Abstract + }; - codeTypeDeclaration.IsClass = true; - codeTypeDeclaration.TypeAttributes = TypeAttributes.Public | TypeAttributes.Abstract; codeTypeDeclaration.BaseTypes.Add(typeof(INotifyPropertyChanged)); codeTypeDeclaration.BaseTypes.Add(typeof(INotifyPropertyChanging)); codeTypeDeclaration.BaseTypes.Add(typeof(IEntity)); @@ -58,8 +61,10 @@ public CodeTypeDeclaration CreateClass() private static void AddConstructor(CodeTypeDeclaration declaration) { - CodeConstructor constructor = new CodeConstructor(); - constructor.Attributes = MemberAttributes.Public; + var constructor = new CodeConstructor + { + Attributes = MemberAttributes.Public + }; constructor.Statements.Add( new CodeAssignStatement( @@ -78,16 +83,18 @@ private static void AddConstructor(CodeTypeDeclaration declaration) private void AddIEntityImplementation(CodeTypeDeclaration declaration) { - CodeMemberMethod method = new CodeMemberMethod(); - method.Name = "Commit"; - method.Attributes = MemberAttributes.Public | MemberAttributes.Final; + var method = new CodeMemberMethod + { + Name = "Commit", + Attributes = MemberAttributes.Public | MemberAttributes.Final + }; foreach (DataFieldDescriptor dataFieldDescriptor in _dataTypeDescriptor.Fields) { string propertyName = dataFieldDescriptor.Name; - string fieldName = string.Format("_{0}", propertyName); - string nullableFieldName = string.Format("_{0}Nullable", propertyName); + string fieldName = $"_{propertyName}"; + string nullableFieldName = $"_{propertyName}Nullable"; method.Statements.Add( new CodeConditionStatement( @@ -144,17 +151,20 @@ private void AddIEntityImplementation(CodeTypeDeclaration declaration) private void AddIDataSourceProperty(CodeTypeDeclaration declaration) { - PropertyInfo propertyInfo = typeof(IData).GetProperty("DataSourceId"); - - CodeMemberProperty codeProperty = new CodeMemberProperty(); - codeProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final; - codeProperty.Name = propertyInfo.Name; - codeProperty.HasGet = true; - codeProperty.HasSet = false; - codeProperty.Type = new CodeTypeReference(propertyInfo.PropertyType); + PropertyInfo propertyInfo = typeof(IData).GetProperty(nameof(IData.DataSourceId)); - List dataIdConstructorParms = new List(); - foreach (string propertyName in _dataTypeDescriptor.KeyPropertyNames) + var codeProperty = new CodeMemberProperty + { + Attributes = MemberAttributes.Public | MemberAttributes.Final, + Name = propertyInfo.Name, + HasGet = true, + HasSet = false, + Type = new CodeTypeReference(propertyInfo.PropertyType) + }; + + var dataIdConstructorParms = new List(); + foreach (string propertyName in _dataTypeDescriptor.KeyPropertyNames + .Concat(_dataTypeDescriptor.VersionKeyPropertyNames)) { dataIdConstructorParms.Add( new CodePropertyReferenceExpression( @@ -221,8 +231,8 @@ private void AddProperties(CodeTypeDeclaration declaration) string propertyName = dataFieldDescriptor.Name; Type propertyType = dataFieldDescriptor.InstanceType; - string fieldName = string.Format("_{0}", propertyName); - string nullableFieldName = string.Format("_{0}Nullable", propertyName); + string fieldName = $"_{propertyName}"; + string nullableFieldName = $"_{propertyName}Nullable"; AddPropertiesAddField(declaration, propertyType, fieldName); AddPropertiesAddNullableField(declaration, propertyType, nullableFieldName); @@ -234,10 +244,12 @@ private void AddProperties(CodeTypeDeclaration declaration) private static void AddPropertiesAddField(CodeTypeDeclaration declaration, Type propertyType, string fieldName) { - CodeMemberField fieldMember = new CodeMemberField(); - fieldMember.Name = fieldName; - fieldMember.Type = new CodeTypeReference(propertyType); - fieldMember.Attributes = MemberAttributes.Family; + var fieldMember = new CodeMemberField + { + Name = fieldName, + Type = new CodeTypeReference(propertyType), + Attributes = MemberAttributes.Family + }; declaration.Members.Add(fieldMember); } @@ -246,12 +258,14 @@ private static void AddPropertiesAddField(CodeTypeDeclaration declaration, Type private static void AddPropertiesAddNullableField(CodeTypeDeclaration declaration, Type propertyType, string fieldName) { - CodeMemberField fieldMember = new CodeMemberField(); - fieldMember.Name = fieldName; - fieldMember.Type = new CodeTypeReference(typeof(ExtendedNullable<>).FullName, new CodeTypeReference(propertyType)); - fieldMember.Attributes = MemberAttributes.Family; + var fieldMember = new CodeMemberField + { + Name = fieldName, + Type = new CodeTypeReference(typeof (ExtendedNullable<>).FullName, new CodeTypeReference(propertyType)), + Attributes = MemberAttributes.Family, + InitExpression = new CodePrimitiveExpression(null) + }; - fieldMember.InitExpression = new CodePrimitiveExpression(null); declaration.Members.Add(fieldMember); } @@ -260,12 +274,14 @@ private static void AddPropertiesAddNullableField(CodeTypeDeclaration declaratio private static void AddPropertiesAddProperty(CodeTypeDeclaration declaration, string propertyName, Type propertyType, string fieldName, string nullableFieldName) { - CodeMemberProperty propertyMember = new CodeMemberProperty(); - propertyMember.Name = propertyName; - propertyMember.Type = new CodeTypeReference(propertyType); - propertyMember.Attributes = MemberAttributes.Public; - propertyMember.HasSet = true; - propertyMember.HasGet = true; + var propertyMember = new CodeMemberProperty + { + Name = propertyName, + Type = new CodeTypeReference(propertyType), + Attributes = MemberAttributes.Public, + HasSet = true, + HasGet = true + }; propertyMember.GetStatements.Add( diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index eff5c0bc38..5a98a1dc85 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -382,12 +382,12 @@ public Dictionary> GetParents(IEnumerable< foreach (EntityToken entityToken in entityTokens) { - DataEntityToken dataEntityToken = entityToken as DataEntityToken; - + var dataEntityToken = (DataEntityToken) entityToken; Type type = dataEntityToken.InterfaceType; + if (type != typeof(IPage)) continue; - Guid pageId = (Guid) dataEntityToken.DataSourceId.GetKeyValue(); + Guid pageId = (Guid) dataEntityToken.DataSourceId.GetKeyValue(nameof(IPage.Id)); Guid parentPageId = PageManager.GetParentId(pageId); if (parentPageId != Guid.Empty) continue; @@ -402,7 +402,7 @@ public Dictionary> GetParents(IEnumerable< private IEnumerable GetChildElements(EntityToken entityToken, IEnumerable childPageElements) { Guid? itemId = GetParentPageId(entityToken); - if (itemId.HasValue == false) return new Element[] { }; + if (!itemId.HasValue) return new Element[] { }; List associatedChildElements; if (itemId.Value != Guid.Empty) @@ -444,9 +444,7 @@ private IEnumerable GetChildElements(EntityToken entityToken, IEnumerab { IPage parentPage = ((DataEntityToken)entityToken).Data as IPage; - if (parentPage == null) return null; - - return parentPage.Id; + return parentPage?.Id; } throw new NotImplementedException(); diff --git a/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/IDataGenerated/GetXml.cs b/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/IDataGenerated/GetXml.cs index caf254ac3a..1dc54edfe2 100644 --- a/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/IDataGenerated/GetXml.cs +++ b/Composite/Plugins/Functions/FunctionProviders/StandardFunctionProvider/IDataGenerated/GetXml.cs @@ -757,7 +757,7 @@ private static IReferencedDataHelper BuildImpl(string foreignKeyProperty Type destinationType = keyInfo.TargetType; PropertyInfo sourceKeyPropertyInfo = sourceType.GetDataPropertyRecursivly(foreignKeyPropertyName); - PropertyInfo destinationKeyPropertyInfo = destinationType.GetKeyProperties().Single(); + PropertyInfo destinationKeyPropertyInfo = destinationType.GetSingleKeyProperty(); Type keyFieldType = destinationKeyPropertyInfo.PropertyType; Type[] genericArgs = new Type[] { typeof(SOURCE), destinationType, keyFieldType }; diff --git a/Composite/Plugins/Routing/InternalUrlConverters/DataInternalUrlConverter.cs b/Composite/Plugins/Routing/InternalUrlConverters/DataInternalUrlConverter.cs index 22e0205d4b..bf363e0498 100644 --- a/Composite/Plugins/Routing/InternalUrlConverters/DataInternalUrlConverter.cs +++ b/Composite/Plugins/Routing/InternalUrlConverters/DataInternalUrlConverter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using Composite.Core.Routing; using Composite.Core.Types; @@ -10,7 +9,6 @@ namespace Composite.Plugins.Routing.InternalUrlConverters { class DataInternalUrlConverter: IInternalUrlConverter { - private readonly IEnumerable _urlPrefixes; private readonly Type _type; private readonly Type _keyType; private readonly ConstructorInfo _dataReferenceConstructor; @@ -18,18 +16,15 @@ class DataInternalUrlConverter: IInternalUrlConverter public DataInternalUrlConverter(string shortTypeName, Type type) { - _urlPrefixes = new[] { shortTypeName + "(" }; + AcceptedUrlPrefixes = new[] { shortTypeName + "(" }; _type = type; - _keyType = _type.GetKeyProperties().Single().PropertyType; + _keyType = _type.GetSingleKeyProperty().PropertyType; _dataReferenceConstructor = typeof(DataReference<>).MakeGenericType(_type).GetConstructor(new[] { typeof(object) }); } - public IEnumerable AcceptedUrlPrefixes - { - get { return _urlPrefixes; } - } + public IEnumerable AcceptedUrlPrefixes { get; } public string ToPublicUrl(string internalDataUrl, UrlSpace urlSpace) From f9eaa2f0dcd0d72545f75e7f3ace75b9031263aa Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Thu, 14 Apr 2016 16:33:26 +0200 Subject: [PATCH 002/213] Dmitry: Fixing Setup --- .../DataPackageFragmentInstaller.cs | 14 ++++++++------ Composite/Data/DynamicTypes/DataTypeDescriptor.cs | 11 ----------- Composite/Data/Types/IVersioned.cs | 2 +- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs index 6d8cd2797e..a1d8ab9300 100644 --- a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs +++ b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs @@ -413,16 +413,17 @@ private void ValidateNonDynamicAddedType(DataType dataType) DataTypeDescriptor dataTypeDescriptor = DynamicTypeManager.BuildNewDataTypeDescriptor(dataType.InterfaceType); - List requiredPropertyNames = - (from dfd in dataTypeDescriptor.Fields - where !dfd.IsNullable - select dfd.Name).ToList(); - List nonRequiredPropertyNames = + bool isVersionedDataType = typeof (IVersioned).IsAssignableFrom(dataType.InterfaceType); + + var requiredPropertyNames = (from dfd in dataTypeDescriptor.Fields - where dfd.IsNullable + where !dfd.IsNullable && !(isVersionedDataType && dfd.Name == nameof(IVersioned.VersionId)) // Compatibility fix select dfd.Name).ToList(); + var nonRequiredPropertyNames = dataTypeDescriptor.Fields.Select(f => f.Name) + .Except(requiredPropertyNames).ToList(); + foreach (XElement addElement in dataType.Dataset) { @@ -590,6 +591,7 @@ private static bool IsObsoleteField(DataType dataType, string fieldName) return typeof(ILocalizedControlled).IsAssignableFrom(dataType.InterfaceType) && fieldName == "CultureName"; } + private static Dictionary GetDataTypeProperties(Type type) { return type.GetPropertiesRecursively().Where(property => !IsObsoleteProperty(property)).ToDictionary(prop => prop.Name); diff --git a/Composite/Data/DynamicTypes/DataTypeDescriptor.cs b/Composite/Data/DynamicTypes/DataTypeDescriptor.cs index f6efa2ff9c..52f1dba190 100644 --- a/Composite/Data/DynamicTypes/DataTypeDescriptor.cs +++ b/Composite/Data/DynamicTypes/DataTypeDescriptor.cs @@ -496,17 +496,6 @@ public void Validate() if (this.DataScopes.Count == 0) throw new InvalidOperationException("The DataScopes list containing the list of data scopes this type must support can not be empty. Please provide at least one data scopes."); if (this.DataScopes.Select(f => f.Name).Distinct().Count() != this.DataScopes.Count) throw new InvalidOperationException("The DataScopes list contains redundant data scopes"); - if (this.DataScopes.Any(f => f.Equals(DataScopeIdentifier.Public))) - { - foreach (PropertyInfo propertyInfo in typeof(IPublishControlled).GetProperties()) - { - if (!this.Fields.Any(f => f.Name == propertyInfo.Name)) - { - throw new InvalidOperationException($"DataScope '{DataScopeIdentifier.Public}' require you to implement '{typeof (IPublishControlled)}' and a field named '{propertyInfo.Name} is missing"); - } - } - } - this.KeyPropertyNames.ValidateMembers(); this.StoreSortOrderFieldNames.ValidateMembers(); diff --git a/Composite/Data/Types/IVersioned.cs b/Composite/Data/Types/IVersioned.cs index abb9c42d2e..c5d98c6625 100644 --- a/Composite/Data/Types/IVersioned.cs +++ b/Composite/Data/Types/IVersioned.cs @@ -12,6 +12,6 @@ public interface IVersioned : IData [StoreFieldType(PhysicalStoreFieldType.Guid)] [ImmutableFieldId("{f42d511b-1d76-4c05-a895-99f23b757e1e}")] [DefaultFieldNewGuidValue] - Guid VersionId { get; } + Guid VersionId { get; set; } } } From 719f8d6293a9ad8c2cb6dd2edfefbf237d4a8071 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Tue, 19 Apr 2016 16:05:53 +0200 Subject: [PATCH 003/213] Version Publishing Package --- .../Composite.VersionPublishing.csproj | 64 +++++++++++++++ .../IVersionFilteringSettings.cs | 35 ++++++++ .../Properties/AssemblyInfo.cs | 36 +++++++++ Composite.VersionPublishing/StartupHandler.cs | 15 ++++ .../VersionFilteringDataInterceptor.cs | 63 +++++++++++++++ .../VersioningServiceSettings.cs | 80 +++++++++++++++++++ 6 files changed, 293 insertions(+) create mode 100644 Composite.VersionPublishing/Composite.VersionPublishing.csproj create mode 100644 Composite.VersionPublishing/IVersionFilteringSettings.cs create mode 100644 Composite.VersionPublishing/Properties/AssemblyInfo.cs create mode 100644 Composite.VersionPublishing/StartupHandler.cs create mode 100644 Composite.VersionPublishing/VersionFilteringDataInterceptor.cs create mode 100644 Composite.VersionPublishing/VersioningServiceSettings.cs diff --git a/Composite.VersionPublishing/Composite.VersionPublishing.csproj b/Composite.VersionPublishing/Composite.VersionPublishing.csproj new file mode 100644 index 0000000000..4c1b92200f --- /dev/null +++ b/Composite.VersionPublishing/Composite.VersionPublishing.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6} + Library + Properties + Composite.VersionPublishing + Composite.VersionPublishing + v4.5.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {f8d87a5f-d090-4d24-80c1-dbcd938c6cab} + Composite + + + + + \ No newline at end of file diff --git a/Composite.VersionPublishing/IVersionFilteringSettings.cs b/Composite.VersionPublishing/IVersionFilteringSettings.cs new file mode 100644 index 0000000000..c2c1e60210 --- /dev/null +++ b/Composite.VersionPublishing/IVersionFilteringSettings.cs @@ -0,0 +1,35 @@ +using System; +using Composite.Data; + +namespace Composite.VersionPublishing +{ + public interface IVersionFilteringSettings + { + VersionFilteringMode FilteringMode { get; } + DateTime? Time { get; } + string VersionName { get; } + } + + public class VersionFilteringSettings : IVersionFilteringSettings + { + public VersionFilteringMode FilteringMode { get; set; } + public DateTime? Time { get; set; } + public string VersionName { get; set; } + } + + public enum VersionFilteringMode + { + /// + /// No filtering should be applied + /// + None = 0, + /// + /// Only currently published data should be returned + /// + Published = 1, + /// + /// Only relevant data should be returned + /// + Relevant = 2 + } +} diff --git a/Composite.VersionPublishing/Properties/AssemblyInfo.cs b/Composite.VersionPublishing/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..cca5525090 --- /dev/null +++ b/Composite.VersionPublishing/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Composite.VersionPublishing")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Composite.VersionPublishing")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("195ea55c-7621-42da-b07e-8b48927c7ad6")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Composite.VersionPublishing/StartupHandler.cs b/Composite.VersionPublishing/StartupHandler.cs new file mode 100644 index 0000000000..d73f1253eb --- /dev/null +++ b/Composite.VersionPublishing/StartupHandler.cs @@ -0,0 +1,15 @@ +using System; + +namespace Composite.VersionPublishing +{ + public class StartupHandler + { + public static void OnBeforeInitialize() + { + } + + public static void OnInitialized() + { + } + } +} diff --git a/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs b/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs new file mode 100644 index 0000000000..db51f2c016 --- /dev/null +++ b/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs @@ -0,0 +1,63 @@ +using System; +using System.Linq; +using Composite.Data; +using Composite.Data.Types; + +namespace Composite.VersionPublishing +{ + public class PageVersionFilteringDataInterceptor : DataInterceptor + { + public override IQueryable InterceptGetData(IQueryable query) + { + if (typeof (T) == typeof (IPage)) + { + return FilterPages((IQueryable) query) as IQueryable; + } + + return query; + } + + private static IQueryable FilterPages(IQueryable query) + { + using (DataConnection dataConnection = new DataConnection()) + { + var setting = dataConnection.GetService(typeof (VersioningServiceSettings)); + if ((setting as VersioningServiceSettings) == null) + return query; + var vfs = (setting as VersioningServiceSettings).VersionFilteringSettings; + + if (vfs.VersionName != null) + { + return query. + Where( + page => + page.VersionName == vfs.VersionName || + (page.PublishTime <= DateTime.Now && page.UnpublishTime >= DateTime.Now)) + .GroupBy(p => p.Id) + .Select( + g => + g.OrderByDescending(page => page.VersionName == vfs.VersionName) + .ThenByDescending(page => page.PublishTime) + .First()); + } + switch (vfs.FilteringMode) + { + case VersionFilteringMode.Published: + return query + .Where(page => (page.PublishTime == null || page.PublishTime <= vfs.Time) + && (page.UnpublishTime == null || page.UnpublishTime >= vfs.Time)) + .GroupBy(p => p.Id) + .Select(g => g.OrderByDescending(page => page.PublishTime ?? DateTime.MinValue).First()); + case VersionFilteringMode.None: + return query; + case VersionFilteringMode.Relevant: + return query + .GroupBy(p => p.Id) + .Select(g => g.OrderByDescending(page => page.PublishTime ?? DateTime.MinValue).First()); + } + + return query; + } + } + } +} diff --git a/Composite.VersionPublishing/VersioningServiceSettings.cs b/Composite.VersionPublishing/VersioningServiceSettings.cs new file mode 100644 index 0000000000..a5381c2c1a --- /dev/null +++ b/Composite.VersionPublishing/VersioningServiceSettings.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Composite.Data; +using Composite.Data.Types; + +namespace Composite.VersionPublishing +{ + public class VersioningServiceSettings + { + private readonly VersionFilteringSettings _versionFilteringSettings; + public VersionFilteringSettings VersionFilteringSettings => _versionFilteringSettings; + + public static VersioningServiceSettings NoFiltering() + { + return new VersioningServiceSettings(VersionFilteringMode.None, DateTime.Now); + } + + public static VersioningServiceSettings Published(DateTime time) + { + return new VersioningServiceSettings(VersionFilteringMode.Published, time); + } + + public static VersioningServiceSettings Published() + { + return new VersioningServiceSettings(VersionFilteringMode.Published, DateTime.Now); + } + + public static VersioningServiceSettings MostRelevant(DateTime time) + { + return new VersioningServiceSettings(VersionFilteringMode.Relevant, time); + } + + public static VersioningServiceSettings MostRelevant() + { + return new VersioningServiceSettings(VersionFilteringMode.Relevant, DateTime.Now); + } + + public static VersioningServiceSettings ByName(string versionName) + { + return new VersioningServiceSettings(versionName); + } + + private VersioningServiceSettings(string versionName) + { + if (!DataFacade.HasDataInterceptor())//TODO: should I care + DataFacade.SetDataInterceptor(new PageVersionFilteringDataInterceptor()); + + _versionFilteringSettings = new VersionFilteringSettings + { + FilteringMode = VersionFilteringMode.Published, + VersionName = versionName + }; + } + + private VersioningServiceSettings(VersionFilteringMode filteringMode, DateTime time) + { + if(!DataFacade.HasDataInterceptor())//TODO: should I care + DataFacade.SetDataInterceptor(new PageVersionFilteringDataInterceptor()); + + _versionFilteringSettings = new VersionFilteringSettings + { + FilteringMode = filteringMode, + Time = time + }; + } + + public void ChangeProperties(VersionFilteringMode filteringMode, DateTime? time) + { + _versionFilteringSettings.FilteringMode = filteringMode; + if (time != null) + { + _versionFilteringSettings.Time = time; + } + } + + } +} From 3cd0a35690ef166e448b11c67da63bbcb6ecc7ce Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Tue, 19 Apr 2016 16:07:40 +0200 Subject: [PATCH 004/213] introducing service for data scopes --- Composite/Composite.csproj | 2 + .../DataConnectionImplementation.cs | 2 +- .../Implementation/ImplementationFactory.cs | 10 +-- Composite/Data/DataConnection.cs | 27 ++++++-- Composite/Data/DataScope.cs | 16 +++++ Composite/Data/DataServiceScopeManager.cs | 61 +++++++++++++++++++ Composite/Data/Types/ITimedPublishing.cs | 2 +- 7 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 Composite/Data/DataServiceScopeManager.cs diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 710bf09789..a4c45e47ff 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -164,6 +164,8 @@ + + diff --git a/Composite/Core/Implementation/DataConnectionImplementation.cs b/Composite/Core/Implementation/DataConnectionImplementation.cs index aa41c0d1c8..52eff5c57f 100644 --- a/Composite/Core/Implementation/DataConnectionImplementation.cs +++ b/Composite/Core/Implementation/DataConnectionImplementation.cs @@ -16,7 +16,7 @@ public class DataConnectionImplementation : DataConnectionBase, IDisposable private IDisposable _threadDataManager; private readonly DataScope _dataScope; - + internal DataScope DataScope => _dataScope; /// /// Stateless constructor. This is used when implementations of static methods needs to be called /// Used when New and AllLocales are called diff --git a/Composite/Core/Implementation/ImplementationFactory.cs b/Composite/Core/Implementation/ImplementationFactory.cs index 9d2d9b2f2f..8b6758de5e 100644 --- a/Composite/Core/Implementation/ImplementationFactory.cs +++ b/Composite/Core/Implementation/ImplementationFactory.cs @@ -2,8 +2,7 @@ using Composite.Data; using System.IO; using System.Text; - - +using System; namespace Composite.Core.Implementation { /// @@ -46,9 +45,12 @@ public virtual DataConnectionImplementation StatelessDataConnection { return new DataConnectionImplementation(); } - } - + } + internal object ResolveService(Type t) + { + return DataServiceScopeManager.GetService(t); + } /// /// Documentation pending diff --git a/Composite/Data/DataConnection.cs b/Composite/Data/DataConnection.cs index dcb6b26836..52a2af0a16 100644 --- a/Composite/Data/DataConnection.cs +++ b/Composite/Data/DataConnection.cs @@ -27,7 +27,26 @@ public class DataConnection : ImplementationContainer _pageDataConnection; private ImplementationContainer _sitemapNavigator; + private static DataConnectionImplementation _connectionImplementation; + + /// + /// Resolve service of a specific type that is attached to connection's data scope + /// + /// + /// + public object GetService(Type t) + { + return ImplementationFactory.CurrentFactory.ResolveService(t); + } + /// + /// attach service to data connection + /// + /// + public void AddService(object service) + { + _connectionImplementation.DataScope.AddService(service); + } /// /// Creates a new instance inheriting the @@ -48,7 +67,7 @@ public class DataConnection : ImplementationContainer /// public DataConnection() - : base(() => ImplementationFactory.CurrentFactory.CreateDataConnection(null, null)) + : base(() => _connectionImplementation = ImplementationFactory.CurrentFactory.CreateDataConnection(null, null)) { CreateImplementation(); @@ -76,7 +95,7 @@ public DataConnection() /// /// public DataConnection(PublicationScope scope) - : base(() => ImplementationFactory.CurrentFactory.CreateDataConnection(scope, null)) + : base(() => _connectionImplementation = ImplementationFactory.CurrentFactory.CreateDataConnection(scope, null)) { if ((scope < PublicationScope.Unpublished) || (scope > PublicationScope.Published)) throw new ArgumentOutOfRangeException("scope"); @@ -106,7 +125,7 @@ public DataConnection(PublicationScope scope) /// /// public DataConnection(CultureInfo locale) - : base(() => ImplementationFactory.CurrentFactory.CreateDataConnection(null, locale)) + : base(() => _connectionImplementation = ImplementationFactory.CurrentFactory.CreateDataConnection(null, locale)) { CreateImplementation(); @@ -135,7 +154,7 @@ public DataConnection(CultureInfo locale) /// /// public DataConnection(PublicationScope scope, CultureInfo locale) - : base(() => ImplementationFactory.CurrentFactory.CreateDataConnection(scope, locale)) + : base(() => _connectionImplementation = ImplementationFactory.CurrentFactory.CreateDataConnection(scope, locale)) { if ((scope < PublicationScope.Unpublished) || (scope > PublicationScope.Published)) throw new ArgumentOutOfRangeException("scope"); diff --git a/Composite/Data/DataScope.cs b/Composite/Data/DataScope.cs index 487ed17789..3a93a7f962 100644 --- a/Composite/Data/DataScope.cs +++ b/Composite/Data/DataScope.cs @@ -12,7 +12,18 @@ public sealed class DataScope : IDisposable { private readonly bool _dataScopePushed = false; private readonly bool _cultureInfoPushed = false; + private bool _dataServicePushed = false; + /// + public void AddService(object service) + { + if (!_dataServicePushed) + { + DataServiceScopeManager.PushDataServiceScope(); + _dataServicePushed = true; + } + DataServiceScopeManager.AddService(service); + } /// public DataScope(DataScopeIdentifier dataScope) @@ -93,6 +104,11 @@ public void Dispose() { LocalizationScopeManager.PopLocalizationScope(); } + + if (_dataServicePushed) + { + DataServiceScopeManager.PopDataServiceScope(); + } } } } diff --git a/Composite/Data/DataServiceScopeManager.cs b/Composite/Data/DataServiceScopeManager.cs new file mode 100644 index 0000000000..8f359cdefc --- /dev/null +++ b/Composite/Data/DataServiceScopeManager.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Remoting.Messaging; +using Composite.Core.Caching; + +namespace Composite.Data +{ + /// + /// + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public static class DataServiceScopeManager + { + internal static void AddService(object service) + { + DataServiceScopeStack.Peek().Add(service); + } + + internal static object GetService(Type t) + { + var stack = DataServiceScopeStack.GetEnumerator(); + while (stack.MoveNext()) + { + if (stack.Current?.Find(f => f.GetType() == t) != null) + { + return stack.Current.Last(f => f.GetType() == t); + } + } + return null; + } + + private static Stack> DataServiceScopeStack + { + get + { + var threadLocalStack = CallContext.GetData(ThreadLocalCacheKey) as Stack>; + if (threadLocalStack != null) + { + return threadLocalStack; + } + + return RequestLifetimeCache.GetCachedOrNew>>("DataServiceScopeManager:Stack"); + } + } + + private const string ThreadLocalCacheKey = "DataServiceScopeManager:ThreadLocal"; + + + + + internal static void PopDataServiceScope() + { + DataServiceScopeStack.Pop(); + } + internal static void PushDataServiceScope() + { + DataServiceScopeStack.Push(new List()); + } + } +} diff --git a/Composite/Data/Types/ITimedPublishing.cs b/Composite/Data/Types/ITimedPublishing.cs index c3bf7446aa..6a9534fc99 100644 --- a/Composite/Data/Types/ITimedPublishing.cs +++ b/Composite/Data/Types/ITimedPublishing.cs @@ -24,6 +24,6 @@ public interface ITimedPublishing: IVersioned, IData /// [StoreFieldType(PhysicalStoreFieldType.String, 128, IsNullable = true)] [ImmutableFieldId("{70b53ac6-d58e-4624-ab47-975e80853673}")] - string VersionTag { get; set; } + string VersionName { get; set; } } } From 4b738bf5b03baa5ce9801120c6e98ed54d71a327 Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Wed, 20 Apr 2016 14:37:00 +0200 Subject: [PATCH 005/213] Renaming KeyFields property on DataDescriptor to PhysicalKeyFields so it is obvious that this will also return any "sub versioning" key fields. --- Composite/Composite.csproj | 1 - Composite/Data/DynamicTypes/DataTypeDescriptor.cs | 5 +++-- .../CodeGeneration/DataIdClassGenerator.cs | 4 ++-- .../CodeGeneration/EntityBaseClassGenerator.cs | 3 +-- .../CodeGeneration/SqlDataProviderCodeBuilder.cs | 2 +- .../Foundation/InterfaceConfigurationManipulator.cs | 2 +- .../XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs | 6 +++--- .../CodeGeneration/XmlDataProviderCodeBuilder.cs | 2 +- .../Foundation/InterfaceConfigurationManipulator.cs | 2 +- 9 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index a4c45e47ff..529f6b7ea4 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -165,7 +165,6 @@ - diff --git a/Composite/Data/DynamicTypes/DataTypeDescriptor.cs b/Composite/Data/DynamicTypes/DataTypeDescriptor.cs index 52f1dba190..f5b1403b1e 100644 --- a/Composite/Data/DynamicTypes/DataTypeDescriptor.cs +++ b/Composite/Data/DynamicTypes/DataTypeDescriptor.cs @@ -164,9 +164,10 @@ public Type GetInterfaceType() public DataFieldDescriptorCollection Fields { get; set; } /// - /// Key fields. Note that the order of the fields is important. + /// Physical key fields. Note that the order of the fields is important. + /// The physical key ensure that storage identity is unique across different versions of data with shared id. /// - internal IEnumerable KeyFields + internal IEnumerable PhysicalKeyFields { get { diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/DataIdClassGenerator.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/DataIdClassGenerator.cs index af252e1604..c226d691b0 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/DataIdClassGenerator.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/DataIdClassGenerator.cs @@ -51,7 +51,7 @@ private void AddConstructor(CodeTypeDeclaration declaration) { var constructor = new CodeConstructor { Attributes = MemberAttributes.Public | MemberAttributes.Final }; - foreach (var keyField in _dataTypeDescriptor.KeyFields) + foreach (var keyField in _dataTypeDescriptor.PhysicalKeyFields) { Type keyPropertyType = keyField.InstanceType; @@ -67,7 +67,7 @@ private void AddConstructor(CodeTypeDeclaration declaration) private void AddProperties(CodeTypeDeclaration declaration) { - foreach (var keyProperty in _dataTypeDescriptor.KeyFields) + foreach (var keyProperty in _dataTypeDescriptor.PhysicalKeyFields) { string keyPropertyName = keyProperty.Name; Type keyPropertyType = keyProperty.InstanceType; diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityBaseClassGenerator.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityBaseClassGenerator.cs index 1d2fce0c4f..5966622d99 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityBaseClassGenerator.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityBaseClassGenerator.cs @@ -163,8 +163,7 @@ private void AddIDataSourceProperty(CodeTypeDeclaration declaration) }; var dataIdConstructorParms = new List(); - foreach (string propertyName in _dataTypeDescriptor.KeyPropertyNames - .Concat(_dataTypeDescriptor.VersionKeyPropertyNames)) + foreach (string propertyName in _dataTypeDescriptor.PhysicalKeyFields.Select(f=>f.Name)) { dataIdConstructorParms.Add( new CodePropertyReferenceExpression( diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/SqlDataProviderCodeBuilder.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/SqlDataProviderCodeBuilder.cs index c2a3d88cef..46cc5e1e42 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/SqlDataProviderCodeBuilder.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/SqlDataProviderCodeBuilder.cs @@ -57,7 +57,7 @@ internal void AddDataType(DataTypeDescriptor dataTypeDescriptor, IEnumerable(); var keyPropertiesList = new List>(); - foreach (var keyField in dataTypeDescriptor.KeyFields) + foreach (var keyField in dataTypeDescriptor.PhysicalKeyFields) { Verify.That(!keyPropertiesDictionary.ContainsKey(keyField.Name), "Key field with name '{0}' already present. Data type: {1}. Check for multiple [KeyPropertyName(...)] attributes", keyField.Name, dataTypeDescriptor.Namespace + "." + dataTypeDescriptor.Name); diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/InterfaceConfigurationManipulator.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/InterfaceConfigurationManipulator.cs index ec877daf4c..b00ba3ce89 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/InterfaceConfigurationManipulator.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/InterfaceConfigurationManipulator.cs @@ -139,7 +139,7 @@ private static InterfaceConfigurationElement BuildInterfaceConfigurationElement( //} var keyInfo = new SimpleNameTypeConfigurationElementCollection(); - foreach (DataFieldDescriptor field in dataTypeDescriptor.KeyFields) + foreach (DataFieldDescriptor field in dataTypeDescriptor.PhysicalKeyFields) { keyInfo.Add(field.Name, field.InstanceType); } diff --git a/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs b/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs index 61ca1f7ed6..f81ff170f1 100644 --- a/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs +++ b/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs @@ -58,7 +58,7 @@ public CodeTypeDeclaration CreateClass() AddGetHashCodeMethod(declaration); - foreach (DataFieldDescriptor field in _dataTypeDescriptor.KeyFields) + foreach (DataFieldDescriptor field in _dataTypeDescriptor.PhysicalKeyFields) { AddProperty(declaration, field.Name, field.InstanceType); } @@ -73,7 +73,7 @@ internal Dictionary Properties { get { - return _dataTypeDescriptor.KeyFields.ToDictionary(field => field.Name, field => field.InstanceType); + return _dataTypeDescriptor.PhysicalKeyFields.ToDictionary(field => field.Name, field => field.InstanceType); } } @@ -98,7 +98,7 @@ private void AddConstructor(CodeTypeDeclaration declaration) - foreach (var keyField in _dataTypeDescriptor.KeyFields) + foreach (var keyField in _dataTypeDescriptor.PhysicalKeyFields) { string propertyFieldName = MakePropertyFieldName(keyField.Name); string attributeVariableName = "attr" + keyField.Name; diff --git a/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/XmlDataProviderCodeBuilder.cs b/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/XmlDataProviderCodeBuilder.cs index 5ac12a9242..d3aad9b6c1 100644 --- a/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/XmlDataProviderCodeBuilder.cs +++ b/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/XmlDataProviderCodeBuilder.cs @@ -46,7 +46,7 @@ internal void AddDataType(DataTypeDescriptor dataTypeDescriptor) // Property serializer for entity tokens and more var keyPropertiesDictionary = new Dictionary(); var keyPropertiesList = new List>(); - foreach (var keyField in dataTypeDescriptor.KeyFields) + foreach (var keyField in dataTypeDescriptor.PhysicalKeyFields) { Verify.That(!keyPropertiesDictionary.ContainsKey(keyField.Name), "Key field with name '{0}' already present. Data type: {1}. Check for multiple [KeyPropertyName(...)] attributes.", keyField.Name, dataTypeDescriptor.Namespace + "." + dataTypeDescriptor.Name); diff --git a/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/InterfaceConfigurationManipulator.cs b/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/InterfaceConfigurationManipulator.cs index 400093a70e..05948e9b2f 100644 --- a/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/InterfaceConfigurationManipulator.cs +++ b/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/InterfaceConfigurationManipulator.cs @@ -247,7 +247,7 @@ private static XmlProviderInterfaceConfigurationElement BuildXmlProviderInterfac } configurationElement.ConfigurationDataIdProperties = new SimpleNameTypeConfigurationElementCollection(); - foreach (DataFieldDescriptor field in dataTypeDescriptor.KeyFields) + foreach (DataFieldDescriptor field in dataTypeDescriptor.PhysicalKeyFields) { configurationElement.ConfigurationDataIdProperties.Add(field.Name, field.InstanceType); } From 7a6269d3940d7e1aa7ecb10aa3d12c61171851ef Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Wed, 20 Apr 2016 18:22:48 +0200 Subject: [PATCH 006/213] Making XmlDataProvider generate DataIds that take VersionId into account --- .../XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs b/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs index f81ff170f1..2475db0bd9 100644 --- a/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs +++ b/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs @@ -275,7 +275,8 @@ private void AddGetHashCodeMethod(CodeTypeDeclaration declaration) CodeExpression hashCodeExpression = null; - foreach (string keyPropertyName in _dataTypeDescriptor.KeyPropertyNames) +#warnign We DO want IDataId classes to reflect both id and VersionId for data, right? + foreach (string keyPropertyName in _dataTypeDescriptor.PhysicalKeyFields.Select(f=>f.Name)) { string propertyFieldName = MakePropertyFieldName(_dataTypeDescriptor.Fields[keyPropertyName].Name); From 372687cebe8c862c7f2d7000d8cb3b8d7151bfc2 Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Wed, 20 Apr 2016 18:24:03 +0200 Subject: [PATCH 007/213] Adding versioning support on page content --- Composite/Data/Types/IPagePlaceholderContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/Data/Types/IPagePlaceholderContent.cs b/Composite/Data/Types/IPagePlaceholderContent.cs index b86259e032..a497af4bb0 100644 --- a/Composite/Data/Types/IPagePlaceholderContent.cs +++ b/Composite/Data/Types/IPagePlaceholderContent.cs @@ -23,7 +23,7 @@ namespace Composite.Data.Types [CachingAttribute(CachingType.Full)] [PublishProcessControllerTypeAttribute(typeof(GenericPublishProcessController))] [Title("C1 Page Content")] - public interface IPagePlaceholderContent : IData, IChangeHistory, IPublishControlled, ILocalizedControlled + public interface IPagePlaceholderContent : IData, IChangeHistory, IPublishControlled, ILocalizedControlled, IVersioned { /// [StoreFieldType(PhysicalStoreFieldType.Guid)] From 8e5c29120aaefd555ddc24e02e9fe05101a1210e Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Wed, 20 Apr 2016 18:26:09 +0200 Subject: [PATCH 008/213] Enabling adding multiple versions of a page via the PageServices class, but ensuring we do not add the same page id more than once to IPageStructure. This could use a little beautification. --- Composite/Data/Types/PageInsertPosition.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Composite/Data/Types/PageInsertPosition.cs b/Composite/Data/Types/PageInsertPosition.cs index cfae48e1df..4f908c1d74 100644 --- a/Composite/Data/Types/PageInsertPosition.cs +++ b/Composite/Data/Types/PageInsertPosition.cs @@ -46,6 +46,9 @@ internal class BottomPageInsertPosition : IPageInsertionPosition { public void CreatePageStructure(IPage page, Guid parentPageId) { + if (DataFacade.GetData(f => f.Id == page.Id).Any()) + return; + int siblingPageCount = (from ps in DataFacade.GetData() where ps.ParentId == parentPageId @@ -64,6 +67,9 @@ internal class TopPageInsertPosition : IPageInsertionPosition { public void CreatePageStructure(IPage page, Guid parentPageId) { + if (DataFacade.GetData(f => f.Id == page.Id).Any()) + return; + PageServices.InsertIntoPositionInternal(page.Id, parentPageId, 0); } } @@ -72,6 +78,9 @@ internal class AlphabeticPageInsertPosition : IPageInsertionPosition { public void CreatePageStructure(IPage newPage, Guid parentPageId) { + if (DataFacade.GetData(f => f.Id == newPage.Id).Any()) + return; + List pageStructures = (from ps in DataFacade.GetData() where ps.ParentId == parentPageId @@ -135,6 +144,9 @@ public AfterPageInsertPosition(Guid existingPageId) public void CreatePageStructure(IPage newPage, Guid parentPageId) { + if (DataFacade.GetData(f => f.Id == newPage.Id).Any()) + return; + var pageStructures = (from ps in DataFacade.GetData() where ps.ParentId == parentPageId From dced8c60711b6f4906569af9c206b91ad438aa66 Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Wed, 20 Apr 2016 18:31:20 +0200 Subject: [PATCH 009/213] Making EditPageWorkflow take VersionId into account (making it able to edit a specific version of a page) --- .../PageElementProvider/EditPageWorkflow.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs b/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs index ba019af2c3..d45ef37bc3 100644 --- a/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs +++ b/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs @@ -270,7 +270,7 @@ private void editStateCodeActivity_ExecuteCode(object sender, EventArgs e) } - var contents = DataFacade.GetData(f => f.PageId == selectedPage.Id).ToList(); + var contents = DataFacade.GetData(f => f.PageId == selectedPage.Id && f.VersionId == selectedPage.VersionId).ToList(); var namedXhtmlFragments = contents.ToDictionary(content => content.PlaceHolderId, content => content.Content ?? ""); @@ -317,7 +317,7 @@ private void saveCodeActivity_ExecuteCode(object sender, EventArgs e) var updateTreeRefresher = CreateUpdateTreeRefresher(EntityToken); var selectedPage = GetBinding("SelectedPage"); - var originalPage = DataFacade.GetData(f => f.Id == selectedPage.Id).SingleOrDefault(); + var originalPage = DataFacade.GetData(f => f.Id == selectedPage.Id && f.VersionId == selectedPage.VersionId).SingleOrDefault(); var viewLabelUpdated = originalPage == null || selectedPage.MenuTitle != originalPage.MenuTitle @@ -396,7 +396,7 @@ private void saveCodeActivity_ExecuteCode(object sender, EventArgs e) DataFacade.Update(originalPage); var contentDictionary = GetBinding>("NamedXhtmlFragments"); - var existingContents = DataFacade.GetData(f => f.PageId == selectedPage.Id).ToList(); + var existingContents = DataFacade.GetData(f => f.PageId == selectedPage.Id && f.VersionId == selectedPage.VersionId).ToList(); foreach (var existingContent in existingContents) { @@ -416,6 +416,7 @@ private void saveCodeActivity_ExecuteCode(object sender, EventArgs e) { var newContent = DataFacade.BuildNew(); newContent.PageId = selectedPage.Id; + newContent.VersionId = selectedPage.VersionId; newContent.PlaceHolderId = contentDictionaryElement.Key; newContent.Content = contentDictionaryElement.Value; newContent.SourceCultureName = UserSettings.ActiveLocaleCultureInfo.Name; @@ -711,7 +712,7 @@ private static string GetText(string key) private void PageStillExists(object sender, ConditionalEventArgs e) { var selectedPage = GetBinding("SelectedPage"); - var originalPage = DataFacade.GetData(f => f.Id == selectedPage.Id).SingleOrDefault(); + var originalPage = DataFacade.GetData(f => f.Id == selectedPage.Id && f.VersionId == selectedPage.VersionId).SingleOrDefault(); e.Result = originalPage != null; } From 3ff36217216cf12ea571d54b1213ea8ce6782c2a Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Wed, 20 Apr 2016 18:32:02 +0200 Subject: [PATCH 010/213] Making PageServices AddPage handle versioning --- Composite/Data/Types/PageServices.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Composite/Data/Types/PageServices.cs b/Composite/Data/Types/PageServices.cs index 64edf2382a..96018de0e0 100644 --- a/Composite/Data/Types/PageServices.cs +++ b/Composite/Data/Types/PageServices.cs @@ -478,6 +478,7 @@ public static void AddPageTypeRelatedData(IPage page) { IPagePlaceholderContent pagePlaceholderContent = DataFacade.BuildNew(); pagePlaceholderContent.PageId = page.Id; + pagePlaceholderContent.VersionId = page.VersionId; pagePlaceholderContent.PlaceHolderId = pageTypeDefaultPageContent.PlaceHolderId; pagePlaceholderContent.Content = pageTypeDefaultPageContent.Content; DataFacade.AddNew(pagePlaceholderContent); @@ -489,6 +490,8 @@ public static void AddPageTypeRelatedData(IPage page) internal static bool AddPageTypePageFoldersAndApplications(IPage page) { +#warning Validate that having a page type with associated PageType PageFolders or Applications does not break on 2nd add for same page id + Guid pageTypeId = page.PageTypeId; bool treeRefreshindNeeded = false; From ce484da8839b49f250e85a2e68d1f434ed43c879 Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Wed, 20 Apr 2016 18:34:09 +0200 Subject: [PATCH 011/213] Making PageServices GetChildren return all versions of pages for a given parent. Adding bundle concept to ElementVisualData. Making PageElementProvider emit pages in bundles. --- .../Elements/ElementVisualizedData.cs | 11 +++++++++++ Composite/Data/Types/PageServices.cs | 19 ++++++------------- .../PageElementProvider.cs | 4 +++- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Composite/C1Console/Elements/ElementVisualizedData.cs b/Composite/C1Console/Elements/ElementVisualizedData.cs index d7b28db2f2..a7cfa35f44 100644 --- a/Composite/C1Console/Elements/ElementVisualizedData.cs +++ b/Composite/C1Console/Elements/ElementVisualizedData.cs @@ -37,5 +37,16 @@ public sealed class ElementVisualizedData /// Tooltip for the element - typically shown when hovering the element /// public string ToolTip { get; set; } + + /// + /// Having a common ElementBundle across elements will make the client bundle them up as a single node, and allow the user to select a specific element via a drop down, showing individual BundleElementName values + /// + public string ElementBundle { get; set; } + + /// + /// When bundling elements this field is used to identify this specific element for selection + /// + public string BundleElementName { get; set; } + } } diff --git a/Composite/Data/Types/PageServices.cs b/Composite/Data/Types/PageServices.cs index 96018de0e0..05a8de666e 100644 --- a/Composite/Data/Types/PageServices.cs +++ b/Composite/Data/Types/PageServices.cs @@ -56,20 +56,13 @@ public static IQueryable GetChildren(this IPage page) /// public static IQueryable GetChildren(Guid parentId) { - var pageIDs = PageManager.GetChildrenIDs(parentId); + return from ps in DataFacade.GetData() + join p in DataFacade.GetData() on ps.Id equals p.Id + where ps.ParentId == parentId + orderby ps.LocalOrdering + select p; - var result = new List(); - foreach (var id in pageIDs) - { - var page = PageManager.GetPageById(id); - // A page can de deleted after getting the child list, in a separate thread - if (page != null) - { - result.Add(page); - } - } - - return result.AsQueryable(); +#warning revisit this - we return all versions (by design so far). Any ordering on page versions? - check history for original intent } diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index 5a98a1dc85..51d69f2914 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -816,7 +816,9 @@ private ElementVisualizedData MakeVisualData(IPage page, PageLocaleState pageLoc { HasChildren = hasChildren, Label = (isRootPage || string.IsNullOrWhiteSpace(page.MenuTitle)) ? page.Title : page.MenuTitle, - ToolTip = page.Description + ToolTip = page.Description, + ElementBundle = page.Id.ToString(), + BundleElementName = page.VersionName }; if (pageLocaleState == PageLocaleState.Own) From ef854e2596bce1deda71a97f80a947edd3253322 Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Wed, 20 Apr 2016 18:41:19 +0200 Subject: [PATCH 012/213] Sending ElementBundle details to client. --- .../Services/TreeServiceObjects/ClientElement.cs | 10 ++++++++++ .../ExtensionMethods/ElementExtensionMethods.cs | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Composite/Core/WebClient/Services/TreeServiceObjects/ClientElement.cs b/Composite/Core/WebClient/Services/TreeServiceObjects/ClientElement.cs index 1ec3af2ad8..67bbc63d2a 100644 --- a/Composite/Core/WebClient/Services/TreeServiceObjects/ClientElement.cs +++ b/Composite/Core/WebClient/Services/TreeServiceObjects/ClientElement.cs @@ -73,5 +73,15 @@ public sealed class ClientElement /// the client should disregard elements with TreeLockEnabled == true and continue searching. /// public bool TreeLockEnabled { get; set; } + + /// + /// Having a common ElementBundle across elements will make the client bundle them up as a single node, and allow the user to select a specific element via a drop down, showing individual BundleElementName values + /// + public string ElementBundle { get; set; } + + /// + /// When bundling elements this field is used to identify this specific element for selection + /// + public string BundleElementName { get; set; } } } diff --git a/Composite/Core/WebClient/Services/TreeServiceObjects/ExtensionMethods/ElementExtensionMethods.cs b/Composite/Core/WebClient/Services/TreeServiceObjects/ExtensionMethods/ElementExtensionMethods.cs index ebd0d56e6b..ffdfd494dc 100644 --- a/Composite/Core/WebClient/Services/TreeServiceObjects/ExtensionMethods/ElementExtensionMethods.cs +++ b/Composite/Core/WebClient/Services/TreeServiceObjects/ExtensionMethods/ElementExtensionMethods.cs @@ -41,7 +41,9 @@ public static ClientElement GetClientElement(this Element element) PropertyBag = element.PropertyBag.ToClientPropertyBag(), TagValue = element.TagValue, ContainsTaggedActions = element.Actions.Any(f => f.TagValue != null), - TreeLockEnabled = element.TreeLockBehavior == TreeLockBehavior.Normal + TreeLockEnabled = element.TreeLockBehavior == TreeLockBehavior.Normal, + ElementBundle = element.VisualData.ElementBundle, + BundleElementName = element.VisualData.BundleElementName }; clientElement.ActionKeys = From 1c40db82964b52c1036c32ad4d5bc1ef5bb51909 Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Wed, 20 Apr 2016 18:41:37 +0200 Subject: [PATCH 013/213] Fixing type making things compile - yay! --- .../XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs b/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs index 2475db0bd9..b117465885 100644 --- a/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs +++ b/Composite/Plugins/Data/DataProviders/XmlDataProvider/CodeGeneration/DataIdClassGenerator.cs @@ -275,7 +275,7 @@ private void AddGetHashCodeMethod(CodeTypeDeclaration declaration) CodeExpression hashCodeExpression = null; -#warnign We DO want IDataId classes to reflect both id and VersionId for data, right? +#warning We DO want IDataId classes to reflect both id and VersionId for data, right? foreach (string keyPropertyName in _dataTypeDescriptor.PhysicalKeyFields.Select(f=>f.Name)) { string propertyFieldName = MakePropertyFieldName(_dataTypeDescriptor.Fields[keyPropertyName].Name); From a2089f9f7e47cb7137044d5d3efe064bb07cba58 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Thu, 21 Apr 2016 10:32:02 +0200 Subject: [PATCH 014/213] bug fix --- Composite/Data/DataScope.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Composite/Data/DataScope.cs b/Composite/Data/DataScope.cs index 3a93a7f962..132dad39c9 100644 --- a/Composite/Data/DataScope.cs +++ b/Composite/Data/DataScope.cs @@ -108,6 +108,7 @@ public void Dispose() if (_dataServicePushed) { DataServiceScopeManager.PopDataServiceScope(); + _dataServicePushed = false; } } } From 09b565e8a42fcc2bde7322a384b1f1aeed31947a Mon Sep 17 00:00:00 2001 From: neexite Date: Mon, 25 Apr 2016 17:33:10 +0300 Subject: [PATCH 015/213] Group tree items into bundle - first prototype --- .../views/browser/BrowserPageBinding.js | 66 ++++ .../ui/bindings/system/SystemTreeBinding.js | 155 +++++----- .../bindings/system/SystemTreeNodeBinding.js | 283 ++++++++++++------ 3 files changed, 342 insertions(+), 162 deletions(-) diff --git a/Website/Composite/content/views/browser/BrowserPageBinding.js b/Website/Composite/content/views/browser/BrowserPageBinding.js index 045ad587e5..a3356310eb 100644 --- a/Website/Composite/content/views/browser/BrowserPageBinding.js +++ b/Website/Composite/content/views/browser/BrowserPageBinding.js @@ -138,6 +138,72 @@ BrowserPageBinding.prototype.handleBroadcast = function (broadcast, arg) { case BroadcastMessages.SYSTEM_ACTIONPROFILE_PUBLISHED: if (arg.syncHandle == this.getSyncHandle() && !(arg.source instanceof GenericViewBinding) && arg.actionProfile) { var self = this; + + //TODO move this + + if (!this.shadowTree.bundleselector) { + var addressbar = this.bindingWindow.bindingMap.addressbar; + var selector = SelectorBinding.newInstance(this.bindingDocument); + addressbar.bindingElement.parentNode.appendChild(selector.bindingElement); + selector.attach(); + this.shadowTree.bundleselector = selector; + + //TODO Stub for CSS move to style + selector.bindingElement.style.position = "absolute"; + selector.bindingElement.style.right = "0"; + selector.bindingElement.parentNode.style.position = "relative"; + + + + selector.addActionListener(SelectorBinding.ACTION_SELECTIONCHANGED, { + handleAction: (function(action) { + var binding = action.target; + + switch (action.type) { + case SelectorBinding.ACTION_SELECTIONCHANGED: + var bundleValue = binding.getValue(); + var selectedTreeNode = this.getSystemTree().getFocusedTreeNodeBindings().getFirst(); + var selectedBundleNode = null; + + selectedTreeNode.nodes.each(function(node) { + if (bundleValue == node.getHandle()) { + selectedBundleNode = node; + return false; + } + return true; + }, this); + + if (selectedBundleNode) { + selectedTreeNode.node = selectedBundleNode; + selectedTreeNode.isDisabled = selectedBundleNode.isDisabled(); + selectedTreeNode.setLabel(selectedBundleNode.getLabel()); + selectedTreeNode.setToolTip(selectedBundleNode.getToolTip()); + selectedTreeNode.setImage ( selectedTreeNode.computeImage ()); + } + + this.getSystemTree().focusSingleTreeNodeBinding(selectedTreeNode); + break; + } + }).bind(this) + }); + + } + + var selectedTreeNode = this.getSystemTree().getFocusedTreeNodeBindings().getFirst(); + if (selectedTreeNode.nodes && selectedTreeNode.nodes.getLength() > 1) { + var list = new List(); + selectedTreeNode.nodes.each(function(node) { + list.add(new SelectorBindingSelection(node.getLabel(), node.getHandle(), node.getHandle() === selectedTreeNode.node.getHandle())); + }); + this.shadowTree.bundleselector.populateFromList(list); + this.shadowTree.bundleselector.show(); + } else { + this.shadowTree.bundleselector.hide(); + } + + //TODO end move this + + //IE Require timeout for first time setTimeout(function () { self.push(arg.actionProfile.Node, true); diff --git a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js index 3f4bf5aead..249bf6c529 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js @@ -46,7 +46,7 @@ function SystemTreeBinding() { this._defaultTreeNode = null; /** - * Publishing actionprofiles when treenodes get selected? + * Publishing actionprofiles when treenodes get selected? * If set to false, no commands will be relayed to main toolbar. * TODO: Filter commands on server instead! * @type {boolean} @@ -54,7 +54,7 @@ function SystemTreeBinding() { this._isActionProfileAware = true; /** - * Tree position + * Tree position * @type {int} */ this._activePosition = SystemAction.activePositions.NavigatorTree; @@ -83,14 +83,14 @@ function SystemTreeBinding() { this._tempSelectionTimeout = false; /** - * While this._treeNodeBindings index treenodes by unique handle (ElementKey), + * While this._treeNodeBindings index treenodes by unique handle (ElementKey), * this will index groups of treenodes sharing the same EntityToken. * @type {Map>} */ this._entityTokenRegistry = null; /** - * Counting refreshing treenodes so that we may poke the MessageQueue + * Counting refreshing treenodes so that we may poke the MessageQueue * once all nodes are finished (some timeouts involved here). * @type {Map} */ @@ -109,7 +109,7 @@ function SystemTreeBinding() { this.isLockedToEditor = false; /** - * Points to the last treenode that was blurred in an + * Points to the last treenode that was blurred in an * attempt, always to offer a sensible treenode focus. * @type {string} */ @@ -235,9 +235,9 @@ SystemTreeBinding.prototype.handleAction = function (action) { case TreeNodeBinding.ACTION_BLUR: /* - * This should probably be refactored along with the whole - * _focusedTreeNodeBindings setup, but at least we clear - * the toolbar when tree has no focused nodes... + * This should probably be refactored along with the whole + * _focusedTreeNodeBindings setup, but at least we clear + * the toolbar when tree has no focused nodes... */ var self = this; setTimeout(function () { @@ -250,8 +250,8 @@ SystemTreeBinding.prototype.handleAction = function (action) { }, 0); /* - * Backup the node that blurred. This will allow us to - * focus an appropriate treenode on tree focus, even + * Backup the node that blurred. This will allow us to + * focus an appropriate treenode on tree focus, even * when some feature destroyed all tree selection. * @see {SystemTreeBinding#focus} */ @@ -301,13 +301,13 @@ SystemTreeBinding.prototype.getSyncHandle = function () { } else { return this.perspectiveNode.getHandle(); } - + } /** - * By now, something has probably eliminated all tree focus. But since - * we back up the last blurred node, we can restore a sensible focus. + * By now, something has probably eliminated all tree focus. But since + * we back up the last blurred node, we can restore a sensible focus. */ SystemTreeBinding.prototype._attemptRestorableFocus = function () { @@ -319,7 +319,7 @@ SystemTreeBinding.prototype._attemptRestorableFocus = function () { } /** - * Invoked when tree focus changes AND when tree itself recieves the focus + * Invoked when tree focus changes AND when tree itself recieves the focus * AND when lock-tree-to-editor feature updates the treenode focus. */ SystemTreeBinding.prototype._handleSystemTreeFocus = function () { @@ -341,6 +341,22 @@ SystemTreeBinding.prototype._handleSystemTreeFocus = function () { } } +/** + * @param {SystemTreeNodeBinding} treenode + */ +SystemTreeBinding.prototype.getTokens = function (treenode) { + + var tokens = new List(); + if (treenode.nodes) { + treenode.nodes.each(function (node) { + tokens.add(node.getEntityToken()); + }); + } else { + tokens.add(treenode.node.getEntityToken()); + } + return tokens; +} + /** * Register treenode. * @param {SystemTreeNodeBinding} treenode @@ -350,17 +366,21 @@ SystemTreeBinding.prototype.registerTreeNodeBinding = function (treenode) { SystemTreeBinding.superclass.registerTreeNodeBinding.call(this, treenode); /* - * Update entityToken registry so that we may quickly + * Update entityToken registry so that we may quickly * find the treenode(s) based on this property. */ var reg = this._entityTokenRegistry; + this.getTokens(treenode).each(function (token) { + + if (reg.has(token)) { + reg.get(token).add(treenode); + } else { + reg.set(token, new List([treenode])); + } + }, this); + var token = treenode.node.getEntityToken(); - if (reg.has(token)) { - reg.get(token).add(treenode); - } else { - reg.set(token, new List([treenode])); - } /* * This will attempt to restore treenode selection when tree is refreshed. @@ -368,10 +388,10 @@ SystemTreeBinding.prototype.registerTreeNodeBinding = function (treenode) { var focusnode = null; if (this.isLockedToEditor) { - /* - * Treenode re-focus should be determined by the - * entityToken of the selected DockTabBinding. - * Note that unlike the handle (the ElementKey), an + /* + * Treenode re-focus should be determined by the + * entityToken of the selected DockTabBinding. + * Note that unlike the handle (the ElementKey), an * entityToken may occur multiple times in the same tree. */ if (token == StageBinding.entityToken) { @@ -383,7 +403,7 @@ SystemTreeBinding.prototype.registerTreeNodeBinding = function (treenode) { } else { /* - * Treenode gets focused when it matches a previously + * Treenode gets focused when it matches a previously * unregistered, focused treenode. * @see {SystemTreeBinding#unRegisterTreeNodeBinding} */ @@ -402,9 +422,9 @@ SystemTreeBinding.prototype.registerTreeNodeBinding = function (treenode) { } } -/** - * Unregister treenode. If no selected treenodes are left after whatever operation - * occurred, we empty the toolbar and contextmenu. This should be considered a +/** + * Unregister treenode. If no selected treenodes are left after whatever operation + * occurred, we empty the toolbar and contextmenu. This should be considered a * temporary hack until we implement serverside treeselection. * @overloads {TreeBinding#unRegisterTreeNodeBinding} * @param {SystemTreeNodeBinding} treeNodeBinding @@ -417,20 +437,21 @@ SystemTreeBinding.prototype.unRegisterTreeNodeBinding = function (treenode) { * Unregister from entityToken registry. */ var reg = this._entityTokenRegistry; - var token = treenode.node.getEntityToken(); + this.getTokens(treenode).each(function (token) { - if (reg.has(token)) { - var list = reg.get(token); - list.del(treenode); - if (!list.hasEntries()) { - reg.del(token); - } - } else { - this.logger.fatal("SystemTreeBinding out of synch: unRegisterTreeNodeBinding"); - if (Application.isDeveloperMode) { - Dialog.error("Attention Developer", "Tree is out of synch. Please reproduce this bug and file a report."); + if (reg.has(token)) { + var list = reg.get(token); + list.del(treenode); + if (!list.hasEntries()) { + reg.del(token); + } + } else { + this.logger.fatal("SystemTreeBinding out of synch: unRegisterTreeNodeBinding"); + if (Application.isDeveloperMode) { + Dialog.error("Attention Developer", "Tree is out of synch. Please reproduce this bug and file a report."); + } } - } + }, this); /* * This relates to the treenode re-selection hack. @@ -508,7 +529,7 @@ SystemTreeBinding.prototype._computeClipboardSetup = function () { } /** - * Disabling refresh when clipboard is full. Otherwise, a refresh may cause tests + * Disabling refresh when clipboard is full. Otherwise, a refresh may cause tests * for treenode nesting to be corrupted; and parents can be moved to children nodes. * TODO: Fix nesting test instead? */ @@ -527,7 +548,7 @@ SystemTreeBinding.prototype.handleBroadcast = function (broadcast, arg) { SystemTreeBinding.superclass.handleBroadcast.call(this, broadcast, arg); /** - * Doublecheck that this tree is actually focused. Although if + * Doublecheck that this tree is actually focused. Although if * the server transmits a refresh signal, this is not required. */ switch (broadcast) { @@ -581,7 +602,7 @@ SystemTreeBinding.prototype.handleBroadcast = function (broadcast, arg) { } /** - * A tab was activated somewhere on screen. This should + * A tab was activated somewhere on screen. This should * update tree selection and thus toolbar actions. * @param {DockTabBinding} tab */ @@ -596,7 +617,7 @@ SystemTreeBinding.prototype._handleDockTabSelect = function (tab) { } /* - * If the tab was launched by the server, there is a chance we might + * If the tab was launched by the server, there is a chance we might * find a matching treenode. */ if (isVisible) { @@ -621,12 +642,12 @@ SystemTreeBinding.prototype._handleDockTabSelect = function (tab) { var self = this, token = tab.getEntityToken(); this._handleToken = token; } - + } } /** - * Focus the first encountered treenode with a given entityToken + * Focus the first encountered treenode with a given entityToken * in support of the lock-tree-to-editor feature. * @param {string} entityToken * @param {boolean} isSecondAttempt Discourage endless looping @@ -640,7 +661,7 @@ SystemTreeBinding.prototype._focusTreeNodeByEntityToken = function (entityToken, var treenode = null; /* - * Note that we simply select the first available treenode with the given + * Note that we simply select the first available treenode with the given * entityToken MARKED AS FOCUSABLE. This may not be the best, though... */ if (this._entityTokenRegistry.has(entityToken)) { @@ -671,7 +692,7 @@ SystemTreeBinding.prototype._focusTreeNodeByEntityToken = function (entityToken, StatusBar.busy(); /* - * We timeout to lock the GUI while tree is refreshed; this can take some time. + * We timeout to lock the GUI while tree is refreshed; this can take some time. */ var self = this; setTimeout(function() { @@ -697,7 +718,7 @@ SystemTreeBinding.prototype._focusTreeNodeByEntityToken = function (entityToken, SystemTreeBinding.prototype._fetchTreeForEntityToken = function (entityToken) { /* - * Summon fresh nodes from server. + * Summon fresh nodes from server. */ var perspectiveEntityTokens = new List(); if (this._activePosition == SystemAction.activePositions.SelectorTree) { @@ -734,7 +755,7 @@ SystemTreeBinding.prototype._fetchTreeForEntityToken = function (entityToken) { } /* - * Controversially, the TreeService exposes no nested tree + * Controversially, the TreeService exposes no nested tree * structure, so the parsing code can get a little complicated. */ else if (map.hasEntries()) { @@ -743,7 +764,7 @@ SystemTreeBinding.prototype._fetchTreeForEntityToken = function (entityToken) { var oldnodes = this._treeNodeBindings; var newnodes = new Map(); - /* + /* * Handy treenodebuilder function. * @param {TreeNodeBinding} treenode * @param {List} list @@ -754,7 +775,7 @@ SystemTreeBinding.prototype._fetchTreeForEntityToken = function (entityToken) { if (list.hasEntries()) { /* - * TODO: Since the oldnodes check is needed here, + * TODO: Since the oldnodes check is needed here, * do we risk fogging up the display order of nodes? */ list.each(function (node) { @@ -771,9 +792,9 @@ SystemTreeBinding.prototype._fetchTreeForEntityToken = function (entityToken) { } /* - * Iterate map, building treenodes. Fortunately, - * each sequential entry in the map lists nodes that - * must be appended to a *previously* build node... + * Iterate map, building treenodes. Fortunately, + * each sequential entry in the map lists nodes that + * must be appended to a *previously* build node... */ map.each(function (handle, list) { if (oldnodes.has(handle)) { @@ -802,12 +823,12 @@ SystemTreeBinding.prototype._handleCommandBroadcast = function (broadcast, arg) switch (broadcast) { /* - * Note that this broadcast can also be intercepted by the + * Note that this broadcast can also be intercepted by the * {@link SystemPageBinding} in order to refresh the tree root. */ case BroadcastMessages.SYSTEMTREEBINDING_REFRESH: - /* + /* * If arg is present, it implies that MessageQueue invoked the refresh. * Otherwise the action was instantiated by the user (eg contextmenu). */ @@ -864,8 +885,8 @@ SystemTreeBinding.prototype._invokeServerRefresh = function (token) { var list = this._entityTokenRegistry.get(token).reset(); /* - * Broadcast instructs the MessageQueue to delay - * action execution until tree is refreshed. + * Broadcast instructs the MessageQueue to delay + * action execution until tree is refreshed. */ this._refreshToken = token; EventBroadcaster.broadcast(BroadcastMessages.SYSTEMTREEBINDING_REFRESHING, this._refreshToken); @@ -875,9 +896,9 @@ SystemTreeBinding.prototype._invokeServerRefresh = function (token) { this._refreshingTreeNodes.set(treenode.key, true); /* - * Push to next thread in order to give MessageQueue a chance - * to detect whether or not any trees are actually refreshing - * (because if not, it needs to execute the next action now). + * Push to next thread in order to give MessageQueue a chance + * to detect whether or not any trees are actually refreshing + * (because if not, it needs to execute the next action now). */ setTimeout(function () { treenode.refresh(true); @@ -888,8 +909,8 @@ SystemTreeBinding.prototype._invokeServerRefresh = function (token) { /** - * Invoke manual refresh. This was probably caused - * by user clicking the contextmenu refresh item. + * Invoke manual refresh. This was probably caused + * by user clicking the contextmenu refresh item. * Note that we actually refresh the PARENT treenode. * @param {string} token */ @@ -1071,7 +1092,7 @@ SystemTreeBinding.prototype.setLockToEditor = function (isLocked) { SystemTreeBinding.prototype.getOpenSystemNodes = function () { /* - * Add perspective node, ie. this tree (since the + * Add perspective node, ie. this tree (since the * perspective corresponds to this tree in the hierarchy). */ var list = new List([StageBinding.perspectiveNode]); @@ -1112,7 +1133,7 @@ SystemTreeBinding.prototype.focusSingleTreeNodeBinding = function (binding) { /** * Set Action Profile Group - * @param {function} + * @param {function} */ SystemTreeBinding.prototype.setActionGroup = function (value) { @@ -1130,7 +1151,7 @@ SystemTreeBinding.prototype.setActionGroup = function (value) { /** * Compile actionprofile based on the individual actionprofile of all focused treenodes. - * In case of multiple focused treenodes, only SystemActions relevant for *all* focused + * In case of multiple focused treenodes, only SystemActions relevant for *all* focused * treenodes will be included in the result. * @return {Map>} */ diff --git a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js index c19e525c30..0f3515de34 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js @@ -21,19 +21,19 @@ function SystemTreeNodeBinding () { * @type {SystemLogger} */ this.logger = SystemLogger.getLogger ( "SystemTreeNodeBinding" ); - + /** * Associates the treenode to the selected perspective. * @type {SystemNode} */ this.perspectiveNode = null; - + /** * Flipped when the server opens the treenode. * @type {boolean} */ this._isForcedOpen = false; - + /** * @type {SystemNode} */ @@ -49,21 +49,21 @@ function SystemTreeNodeBinding () { * @overloads {TreeNodeBinding#onBindingAttach} */ SystemTreeNodeBinding.prototype.onBindingAttach = function () { - + this.addActionListener ( SystemTreeNodeBinding.ACTION_REFRESHED ); this.subscribe ( BroadcastMessages.SYSTEMTREENODEBINDING_FORCE_OPEN ); - + /* * Is container? * this.isContainer = this.node.hasChildren (); */ - + /* * Is disabled? */ this.isDisabled = this.node.isDisabled (); - + /* * Label. */ @@ -71,7 +71,7 @@ SystemTreeNodeBinding.prototype.onBindingAttach = function () { if ( label ) { this.setLabel ( label ); } - + /* * Tooltip. */ @@ -79,7 +79,7 @@ SystemTreeNodeBinding.prototype.onBindingAttach = function () { if ( toolTip ) { this.setToolTip ( toolTip ); } - + /* * Handle. */ @@ -87,10 +87,10 @@ SystemTreeNodeBinding.prototype.onBindingAttach = function () { if ( handle ) { this.setHandle ( handle ); } - + /* - * Drag type and other stuff. All key-value pairs in the - * propertybag object is assigned to element as attributes. + * Drag type and other stuff. All key-value pairs in the + * propertybag object is assigned to element as attributes. */ var bag = this.node.getPropertyBag (); if ( bag ) { @@ -106,12 +106,12 @@ SystemTreeNodeBinding.prototype.onBindingAttach = function () { } } } - + /* * Invoke super method. */ SystemTreeNodeBinding.superclass.onBindingAttach.call ( this ); - + /* * A sudden perspective awareness. */ @@ -123,15 +123,15 @@ SystemTreeNodeBinding.prototype.onBindingAttach = function () { * @overloads {Binding#_initializeBindingDragAndDropFeatures} */ SystemTreeNodeBinding.prototype._initializeBindingDragAndDropFeatures = function () { - + // Drag type. if ( this.node.hasDragType ()) { - this.setProperty ( - "dragtype", + this.setProperty ( + "dragtype", this.node.getDragType () ); } - + // Drag accept. if ( this.node.hasDragAccept ()) { var dragaccept = ""; @@ -139,15 +139,15 @@ SystemTreeNodeBinding.prototype._initializeBindingDragAndDropFeatures = function while ( list.hasNext ()) { dragaccept += list.getNext (); if ( list.hasNext ()) { - dragaccept += " "; + dragaccept += " "; } } - this.setProperty ( - "dragaccept", + this.setProperty ( + "dragaccept", dragaccept ); } - + SystemTreeNodeBinding.superclass._initializeBindingDragAndDropFeatures.call ( this ); } @@ -157,14 +157,14 @@ SystemTreeNodeBinding.prototype._initializeBindingDragAndDropFeatures = function * @param {Action} action */ SystemTreeNodeBinding.prototype.handleAction = function ( action ) { - + SystemTreeNodeBinding.superclass.handleAction.call ( this, action ); - + switch ( action.type ) { - + /* * TODO: consider this!.............................................. - */ + */ case SystemTreeNodeBinding.ACTION_REFRESHED : if ( action.target == this ) { // TODO: specifically this ....... if ( !this.isOpen ) { @@ -182,24 +182,24 @@ SystemTreeNodeBinding.prototype.handleAction = function ( action ) { * @param {object} arg */ SystemTreeNodeBinding.prototype.handleBroadcast = function ( broadcast, arg ) { - + SystemTreeNodeBinding.superclass.handleBroadcast.call ( this, broadcast, arg ); - + switch ( broadcast ) { case BroadcastMessages.SYSTEMTREENODEBINDING_FORCE_OPEN : if ( arg == this.node.getEntityToken ()) { if ( this.isContainer && !this.isOpen ) { - + /* * Mark as forced opening. */ this._isForcedOpen = true; EventBroadcaster.broadcast ( BroadcastMessages.SYSTEMTREENODEBINDING_FORCING_OPEN, this ); - + /* - * Push to next thread in order to give MessageQueue a chance - * to detect whether or not any nodes are actually opening - * (because if not, it needs to execute the next action now). + * Push to next thread in order to give MessageQueue a chance + * to detect whether or not any nodes are actually opening + * (because if not, it needs to execute the next action now). */ var self = this; setTimeout ( function () { @@ -210,7 +210,7 @@ SystemTreeNodeBinding.prototype.handleBroadcast = function ( broadcast, arg ) { break; } } - + /** * The imageprofile is provided by the SystemNode assigned to this treenode. * @overwrites {TreeNodeBinding#_computeImageProfile} @@ -222,7 +222,7 @@ SystemTreeNodeBinding.prototype._computeImageProfile = function () {} * @return {string} */ SystemTreeNodeBinding.prototype.computeImage = function () { - + var result = null; var profile = this.node.getImageProfile (); if ( profile ) { @@ -244,19 +244,19 @@ SystemTreeNodeBinding.prototype.computeImage = function () { * @param {boolean} isManaged If set to true, the tree will not refresh! */ SystemTreeNodeBinding.prototype.open = function ( isManaged ) { - + var wasOpened = this.isContainer && !this.isOpen; var wasFresh = !this.hasBeenOpened; - + SystemTreeNodeBinding.superclass.open.call ( this ); - + if ( wasOpened && ( wasFresh || SystemTreeBinding.HAS_NO_MEMORY ) && isManaged != true ) { - + /* * Fetch subtree from server. */ this.refresh (); - + /* * If forced open by server, notify the waiting MessageQueue. */ @@ -264,12 +264,12 @@ SystemTreeNodeBinding.prototype.open = function ( isManaged ) { EventBroadcaster.broadcast ( BroadcastMessages.SYSTEMTREENODEBINDING_FORCED_OPEN, this ); this._isForcedOpen = false; } - } + } } /** - * Refreshing treenode content. This method is never executed for nodes - * with !HasChildren. Method can be invoked as part of treenode opening + * Refreshing treenode content. This method is never executed for nodes + * with !HasChildren. Method can be invoked as part of treenode opening * act, or when the server places a refresh signal on the MessageQueue. */ SystemTreeNodeBinding.prototype.refresh = function () { @@ -288,8 +288,8 @@ SystemTreeNodeBinding.prototype.refresh = function () { StatusBar.busy(); /* - * We timeout to lock the GUI while tree - * is refreshed; this can take some time. + * We timeout to lock the GUI while tree + * is refreshed; this can take some time. */ var self = this; setTimeout ( function () { @@ -308,33 +308,44 @@ SystemTreeNodeBinding.prototype.refresh = function () { * Perform refresh (isolated so that we can invoke on a timeout). * @param {List} branch */ -SystemTreeNodeBinding.prototype._performRefresh = function ( branch ) { - +SystemTreeNodeBinding.prototype._performRefresh = function (branch) { + + //TODO: Refactor this + this.activeBundles = new List(); + this.getDescendantBindingsByType(SystemTreeNodeBinding).each(function (treenode) { + //TODO refactor + if (treenode.nodes && treenode.nodes.getLength() > 1) { + this.activeBundles.add(treenode.node.getHandle()); + } + },this); + //this.empty (); if ( branch != null ) { this._refreshBranch ( branch ); } else { this._refreshChildren (); } - + this.isRefreshing = false; - + + this.activeBundles = null; + /* * TODO: this is hacked! The ISCONTAINER state should be determined by the server! */ this.isContainer = DOMUtil.getElementsByTagName ( this.bindingElement, "treenode" ).item ( 0 ) != null; this.updateClassNames (); - + /* * This will force any closed ancestor treenode to refresh when opened. - * The action will be consumed by ancestor, so we need to dispatch yet + * The action will be consumed by ancestor, so we need to dispatch yet * another action. */ this.dispatchAction ( SystemTreeNodeBinding.ACTION_REFRESHED ); - + /* - * This will inform the tree the we are finished, - * which in turn will inform the MessageQueue. + * This will inform the tree the we are finished, + * which in turn will inform the MessageQueue. * TODO: Only on a MessageQueue-refresh? */ this.dispatchAction ( SystemTreeNodeBinding.ACTION_REFRESHED_YEAH ); @@ -344,9 +355,10 @@ SystemTreeNodeBinding.prototype._performRefresh = function ( branch ) { * Import children only. */ SystemTreeNodeBinding.prototype._refreshChildren = function () { - + var buffer = new List (); - var children = this.node.getChildren (); + var children = this.node.getChildren(); + this.empty (); if ( children.hasEntries ()) { this._insertTreeNodesRegulated ( children ); @@ -361,21 +373,41 @@ SystemTreeNodeBinding.prototype._insertTreeNodesRegulated = function ( children var count = 0; var expandNodes = new List([]); - + + children = this.groupByBundles(children); + /* - * Constantly shortening the children list while - * inserting treenodes. This will let us store - * the remaining list in a buffer when max count + * Constantly shortening the children list while + * inserting treenodes. This will let us store + * the remaining list in a buffer when max count * is reached. */ while ( children.hasEntries () && count <= SystemTreeNodeBinding.MAX_CHILD_IMPORT ) { - var treenode = SystemTreeNodeBinding.newInstance ( - children.extractFirst (), + var item = children.extractFirst(); + var nodes = null; + var child = null; + + if (item instanceof SystemNode) { + child = item; + } else if (item instanceof List) { + nodes = item; + nodes.each(function(bundlenode) { + if (this.activeBundles.has(bundlenode.getHandle())) { + child = bundlenode; + return false; + } + return true; + }, this); + child = child ? child : nodes.getFirst(); + } + + var treenode = SystemTreeNodeBinding.newInstance( + child, this.bindingDocument ); treenode.autoExpand = this.autoExpand; - this.add ( treenode ); - treenode.attach (); + this.add(treenode); + //treenode.attach(); count++; // Auto expand tree folders in selection dialogs, when only one folder can be expanded. @@ -385,12 +417,19 @@ SystemTreeNodeBinding.prototype._insertTreeNodesRegulated = function ( children expandNodes.add(treenode); } } + + if (nodes) { + treenode.nodes = nodes; + } + } if ( children.hasEntries ()) { this._insertBufferTreeNode ( children ); } + this.attachRecursive(); + expandNodes.each(function (node) { if (node.isContainer && !node.isOpen) { var self = node; @@ -401,12 +440,43 @@ SystemTreeNodeBinding.prototype._insertTreeNodesRegulated = function ( children }); } +/** + * Group by bundles + * @param {List} nodes + */ +SystemTreeNodeBinding.prototype.groupByBundles = function (nodes) { + var result = new List(); + var bundles = new Map(); + + while ( nodes.hasEntries ()) { + var node = nodes.extractFirst(); + var elementBundle = node._data.ElementBundle; + if (elementBundle) { + var bundle = null; + if (bundles.has(elementBundle)) { + bundle = bundles.get(elementBundle); + } else { + bundle = new List(); + result.add(bundle); + bundles.set(elementBundle, bundle); + } + bundle.add(node); + } else { + result.add(node); + } + } + + //bundles.empty(); ?? + + return result; +} + /** * Insert buffer node. This will expand to a number of treenodes when navigated. * @param {List} children */ SystemTreeNodeBinding.prototype._insertBufferTreeNode = function ( children ) { - + alert ( "Max treenode count reached. This is not handled!" ); alert ( "TODO: SystemTreeNodeBinding#._insertBufferTreeNode" ); } @@ -416,7 +486,7 @@ SystemTreeNodeBinding.prototype._insertBufferTreeNode = function ( children ) { * @param {List} list A list of open SystemNodes... */ SystemTreeNodeBinding.prototype._refreshBranch = function ( list ) { - + var branch = this.node.getDescendantBranch ( list ); if ( branch.hasEntries ()) { this.XXX ( branch ); @@ -428,20 +498,43 @@ SystemTreeNodeBinding.prototype._refreshBranch = function ( list ) { * @param {List} branch */ SystemTreeNodeBinding.prototype.XXX = function ( branch ) { - + var self = this; var map = new Map (); - + /* - * Note that the parsed branch may have "holes" in the structure. This implies - * that not all may be positioned in the tree. Also note that this is NOT + * Note that the parsed branch may have "holes" in the structure. This implies + * that not all may be positioned in the tree. Also note that this is NOT * regulated according to max child import restrictions! - */ + */ this.empty (); - branch.each ( function ( key, nodes ) { - if ( nodes.hasEntries ()) { - nodes.each ( function ( node ) { - var treenode = SystemTreeNodeBinding.newInstance ( node, self.bindingDocument ); + branch.each(function (key, nodes) { + + var bundles = new Map(); + + if (nodes.hasEntries()) { + nodes = this.groupByBundles(nodes); + nodes.each(function (item) { + var node = null; + var nodes = null; + + + if (item instanceof SystemNode) { + node = item; + } else if (item instanceof List) { + nodes = item; + nodes.each(function (bundlenode) { + if (this.activeBundles.has(bundlenode.getHandle())) { + node = bundlenode; + return false; + } + return true; + }, this); + node = node ? node : nodes.getFirst(); + } + + var treenode = SystemTreeNodeBinding.newInstance(node, self.bindingDocument); + treenode.nodes = nodes; map.set ( node.getHandle (), treenode ); if ( map.has ( key )) { var parent = map.get ( key ); @@ -454,15 +547,15 @@ SystemTreeNodeBinding.prototype.XXX = function ( branch ) { node.searchToken = self.node.searchToken; } else { /* - * Now there is a hole in the structure and the - * SystemNode has no relevance in this context. + * Now there is a hole in the structure and the + * SystemNode has no relevance in this context. * Maybe it was moved somewhere (cut paste scenario). */ } - }); + }, this); } - }); - + }, this); + this.attachRecursive (); branch.dispose (); map.dispose (); @@ -473,7 +566,7 @@ SystemTreeNodeBinding.prototype.XXX = function ( branch ) { * @list {List} */ SystemTreeNodeBinding.prototype.getOpenDescendants = function () { - + var crawler = new TreeCrawler (); var result = new List (); crawler.mode = TreeCrawler.MODE_GETOPEN; @@ -486,15 +579,15 @@ SystemTreeNodeBinding.prototype.getOpenDescendants = function () { } /** - * Get the list of SystemNodes from all descandants. Including + * Get the list of SystemNodes from all descandants. Including * myself in the result because the server needs to know me. * @list {List} */ SystemTreeNodeBinding.prototype.getOpenSystemNodes = function () { - + var result = null; var list = this.getOpenDescendants (); - + if ( list.hasEntries ()) { result = new List ([ this.node ]); // include myself! list.each ( function ( treenode ) { @@ -506,25 +599,25 @@ SystemTreeNodeBinding.prototype.getOpenSystemNodes = function () { } /** - * Small trick to ensure that the treenode twisty will not + * Small trick to ensure that the treenode twisty will not */ SystemTreeNodeBinding.prototype.updateClassNames = function () { - + if ( !this.isRefreshing ) { SystemTreeNodeBinding.superclass.updateClassNames.call ( this ); } } /** - * Accept dragged treenode. + * Accept dragged treenode. * @overwrites {TreeNodeBinding#acceptTreeNodeBinding} * @param {SystemTreeNodeBinding} binding * @param {int} index Optional (omit for drag and drop setup) */ SystemTreeNodeBinding.prototype.acceptTreeNodeBinding = function ( binding, index ) { - + var isCopy = ( SystemTreeBinding.clipboardOperation == SystemTreePopupBinding.CMD_COPY ); - + if ( binding instanceof SystemTreeNodeBinding ) { if ( TreeService.ExecuteDropElementAction ) { TreeService.ExecuteDropElementAction ( @@ -543,19 +636,19 @@ SystemTreeNodeBinding.prototype.acceptTreeNodeBinding = function ( binding, inde * @overloads {TreeNodeBinding#invokeManagedFocus} */ SystemTreeNodeBinding.prototype.invokeManagedFocus = function ( e ) { - + if ( !this.isFocused ) { SystemTreeNodeBinding.superclass.invokeManagedFocus.call ( this ); - + /* - * This broadcast is intercepted by the DockBinding + * This broadcast is intercepted by the DockBinding * who then decides which corresponding tab to highlight. * @see {DockBinding#handleBroadcast} */ var tree = this.containingTreeBinding; if ( tree.isLockedToEditor ) { - EventBroadcaster.broadcast ( - BroadcastMessages.SYSTEMTREENODEBINDING_FOCUS, + EventBroadcaster.broadcast ( + BroadcastMessages.SYSTEMTREENODEBINDING_FOCUS, this ); } @@ -568,7 +661,7 @@ SystemTreeNodeBinding.prototype.invokeManagedFocus = function ( e ) { * @return {boolean} */ SystemTreeNodeBinding.prototype.hasChildren = function () { - + return this.node.hasChildren (); }; From 71efd414e8882d17baab85103d2ea6087fc8121f Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Tue, 26 Apr 2016 16:58:01 +0200 Subject: [PATCH 016/213] binding meta data with versioning feature --- Composite/Data/IPageMetaData.cs | 3 ++- Composite/Data/PageMetaDataFacade.cs | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Composite/Data/IPageMetaData.cs b/Composite/Data/IPageMetaData.cs index 0ddd9407ea..c53eeba43b 100644 --- a/Composite/Data/IPageMetaData.cs +++ b/Composite/Data/IPageMetaData.cs @@ -1,5 +1,6 @@ using Composite.Data.ProcessControlled.ProcessControllers.GenericPublishProcessController; using Composite.Data.ProcessControlled; +using Composite.Data.Types; namespace Composite.Data @@ -12,7 +13,7 @@ namespace Composite.Data [DataScope(DataScopeIdentifier.AdministratedName)] [PublishProcessControllerType(typeof(GenericPublishProcessController))] [DataAssociationAttribute(typeof(Composite.Data.Types.IPage), "PageId", DataAssociationType.Composition)] - public interface IPageMetaData : IPageData, IPublishControlled + public interface IPageMetaData : IPageData, IPublishControlled, IVersioned { /// [StoreFieldType(PhysicalStoreFieldType.String, 128)] diff --git a/Composite/Data/PageMetaDataFacade.cs b/Composite/Data/PageMetaDataFacade.cs index 86f33feee3..5f09a68de3 100644 --- a/Composite/Data/PageMetaDataFacade.cs +++ b/Composite/Data/PageMetaDataFacade.cs @@ -30,6 +30,7 @@ public static class PageMetaDataFacade internal static readonly string MetaDataType_IdFieldName = "Id"; internal static readonly string MetaDataType_PageReferenceFieldName = "PageId"; + internal static readonly string MetaDataType_PageReferenceFieldVersionName = "VersionId"; internal static readonly string MetaDataType_MetaDataDefinitionFieldName = "FieldName"; /// @@ -337,18 +338,19 @@ public static IData GetMetaData(this IPage page, string definitionName, Guid met /// public static IData GetMetaData(this IPage page, string definitionName, Type metaDataType) { - return GetMetaData(page.Id, definitionName, metaDataType); + return GetMetaData(page.Id,page.VersionId, definitionName, metaDataType); } /// - public static IData GetMetaData(Guid pageId, string definitionName, Type metaDataType) + public static IData GetMetaData(Guid pageId,Guid pageVersionId, string definitionName, Type metaDataType) { //TODO: Consider caching here ParameterExpression parameterExpression = Expression.Parameter(metaDataType); LambdaExpression lambdaExpression = Expression.Lambda( + Expression.And( Expression.And( Expression.Equal( Expression.Property( @@ -370,6 +372,17 @@ public static IData GetMetaData(Guid pageId, string definitionName, Type metaDat typeof(Guid) ) ) + ), + Expression.Equal( + Expression.Property( + parameterExpression, + GetDefinitionPageReferencePropertyVersionInfo(metaDataType) + ), + Expression.Constant( + pageVersionId, + typeof(Guid) + ) + ) ), parameterExpression ); @@ -899,6 +912,9 @@ public static void AssignMetaDataSpecificValues(IData metaData, string metaDataD PropertyInfo pageReferencePropertyInfo = GetDefinitionPageReferencePropertyInfo(interfaceType); pageReferencePropertyInfo.SetValue(metaData, definingPage.Id, null); + + PropertyInfo pageReferencePropertyVersionInfo = GetDefinitionPageReferencePropertyVersionInfo(interfaceType); + pageReferencePropertyVersionInfo.SetValue(metaData, definingPage.VersionId, null); } @@ -917,6 +933,11 @@ public static PropertyInfo GetDefinitionPageReferencePropertyInfo(Type metaDataT return metaDataType.GetPropertiesRecursively().Last(f => f.Name == MetaDataType_PageReferenceFieldName); } + /// + public static PropertyInfo GetDefinitionPageReferencePropertyVersionInfo(Type metaDataType) + { + return metaDataType.GetPropertiesRecursively().Last(f => f.Name == MetaDataType_PageReferenceFieldVersionName); + } /// From 64a8fc1b6b8ad668fcf18158473945e77fa7519c Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:36:27 +0200 Subject: [PATCH 017/213] introducing physical key to identify data --- Composite/Data/DataAttributeFacade.cs | 18 ++++++++++ .../Data/Foundation/DataExpressionBuilder.cs | 2 +- CompositeC1.sln | 36 +++++++++++++------ 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/Composite/Data/DataAttributeFacade.cs b/Composite/Data/DataAttributeFacade.cs index b7d3680dd0..29cf4ce3f8 100644 --- a/Composite/Data/DataAttributeFacade.cs +++ b/Composite/Data/DataAttributeFacade.cs @@ -9,6 +9,7 @@ using Composite.Core.Linq; using Composite.Core.ResourceSystem; using Composite.Core.Types; +using Composite.Data.Types; namespace Composite.Data @@ -459,6 +460,23 @@ public static List GetKeyPropertyInfoes(this Type interfaceType) return new List(GetKeyProperties(interfaceType)); } + /// + public static IReadOnlyList GetPhysicalKeyProperties(this Type interfaceType) + { + var versionKeyAttributes = interfaceType.GetCustomAttributes(); + + var versionproperty = (from v in versionKeyAttributes + select v.GetType().GetProperty(v.VersionKeyPropertyName)); + + var res = new List(); + res.AddRange(GetKeyProperties(interfaceType)); + + res.AddRange(versionproperty); + + return res; + + } + /// public static IReadOnlyList GetKeyProperties(this Type interfaceType) { diff --git a/Composite/Data/Foundation/DataExpressionBuilder.cs b/Composite/Data/Foundation/DataExpressionBuilder.cs index dc49916b09..0a861aa5c2 100644 --- a/Composite/Data/Foundation/DataExpressionBuilder.cs +++ b/Composite/Data/Foundation/DataExpressionBuilder.cs @@ -88,7 +88,7 @@ public static Delegate GetWherePredicateDelegate(IData data, bool ignoreVersioni private static LambdaExpression GetWhereLambdaExpression(IData data, bool ignoreVersioning) { - var propertyInfoes = data.DataSourceId.InterfaceType.GetKeyProperties(); + var propertyInfoes = data.DataSourceId.InterfaceType.GetPhysicalKeyProperties(); if (propertyInfoes.Count == 0) { diff --git a/CompositeC1.sln b/CompositeC1.sln index 5270af35a0..899d6aa1b6 100644 --- a/CompositeC1.sln +++ b/CompositeC1.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 14.0.0.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{44113BBC-CB18-41CF-AB26-6D190BA4E117}" ProjectSection(SolutionItems) = preProject @@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Composite.Workflows", "Comp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Composite", "Composite\Composite.csproj", "{F8D87A5F-D090-4D24-80C1-DBCD938C6CAB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Composite.VersionPublishing", "Composite.VersionPublishing\Composite.VersionPublishing.csproj", "{195EA55C-7621-42DA-B07E-8B48927C7AD6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|.NET = Debug|.NET @@ -24,16 +26,6 @@ Global Release|Mixed Platforms = Release|Mixed Platforms EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1FE08476-346A-4053-813D-8807C0E0FC36}.Debug|.NET.ActiveCfg = Debug|Any CPU - {1FE08476-346A-4053-813D-8807C0E0FC36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1FE08476-346A-4053-813D-8807C0E0FC36}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1FE08476-346A-4053-813D-8807C0E0FC36}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {1FE08476-346A-4053-813D-8807C0E0FC36}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {1FE08476-346A-4053-813D-8807C0E0FC36}.Release|.NET.ActiveCfg = Release|Any CPU - {1FE08476-346A-4053-813D-8807C0E0FC36}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1FE08476-346A-4053-813D-8807C0E0FC36}.Release|Any CPU.Build.0 = Release|Any CPU - {1FE08476-346A-4053-813D-8807C0E0FC36}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {1FE08476-346A-4053-813D-8807C0E0FC36}.Release|Mixed Platforms.Build.0 = Release|Any CPU {D0A3D7D5-1D1F-40BC-89C1-93CDCEDF262C}.Debug|.NET.ActiveCfg = Debug|Any CPU {D0A3D7D5-1D1F-40BC-89C1-93CDCEDF262C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D0A3D7D5-1D1F-40BC-89C1-93CDCEDF262C}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -44,6 +36,16 @@ Global {D0A3D7D5-1D1F-40BC-89C1-93CDCEDF262C}.Release|Any CPU.Build.0 = Release|Any CPU {D0A3D7D5-1D1F-40BC-89C1-93CDCEDF262C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {D0A3D7D5-1D1F-40BC-89C1-93CDCEDF262C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1FE08476-346A-4053-813D-8807C0E0FC36}.Debug|.NET.ActiveCfg = Debug|Any CPU + {1FE08476-346A-4053-813D-8807C0E0FC36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FE08476-346A-4053-813D-8807C0E0FC36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FE08476-346A-4053-813D-8807C0E0FC36}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1FE08476-346A-4053-813D-8807C0E0FC36}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1FE08476-346A-4053-813D-8807C0E0FC36}.Release|.NET.ActiveCfg = Release|Any CPU + {1FE08476-346A-4053-813D-8807C0E0FC36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FE08476-346A-4053-813D-8807C0E0FC36}.Release|Any CPU.Build.0 = Release|Any CPU + {1FE08476-346A-4053-813D-8807C0E0FC36}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1FE08476-346A-4053-813D-8807C0E0FC36}.Release|Mixed Platforms.Build.0 = Release|Any CPU {F8D87A5F-D090-4D24-80C1-DBCD938C6CAB}.Debug|.NET.ActiveCfg = Debug|Any CPU {F8D87A5F-D090-4D24-80C1-DBCD938C6CAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F8D87A5F-D090-4D24-80C1-DBCD938C6CAB}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -54,6 +56,18 @@ Global {F8D87A5F-D090-4D24-80C1-DBCD938C6CAB}.Release|Any CPU.Build.0 = Release|Any CPU {F8D87A5F-D090-4D24-80C1-DBCD938C6CAB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {F8D87A5F-D090-4D24-80C1-DBCD938C6CAB}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Debug|.NET.ActiveCfg = Debug|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Debug|.NET.Build.0 = Debug|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Release|.NET.ActiveCfg = Release|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Release|.NET.Build.0 = Release|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Release|Any CPU.Build.0 = Release|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {195EA55C-7621-42DA-B07E-8B48927C7AD6}.Release|Mixed Platforms.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From b66e7e2795a91afd3ca9e59d90d9c041998197d3 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:38:22 +0200 Subject: [PATCH 018/213] introducing global data interceptors --- Composite/Data/DataFacade.cs | 28 ++++++++++ Composite/Data/DataFacadeImpl.cs | 88 ++++++++++++++++++++++++-------- Composite/Data/IDataFacade.cs | 4 ++ 3 files changed, 99 insertions(+), 21 deletions(-) diff --git a/Composite/Data/DataFacade.cs b/Composite/Data/DataFacade.cs index 72a17a9a54..e9e42b7d34 100644 --- a/Composite/Data/DataFacade.cs +++ b/Composite/Data/DataFacade.cs @@ -162,6 +162,34 @@ public static void ClearDataInterceptor(Type interfaceType) methodInfo.Invoke(null, new object[] { }); } + /// + public static void SetGlobalDataInterceptor(DataInterceptor dataInterceptor) + where T : class, IData + { + _dataFacade.SetGlobalDataInterceptor(dataInterceptor); + } + + + + /// + public static bool HasGlobalDataInterceptor() + where T : class, IData + { + return _dataFacade.HasGlobalDataInterceptor(); + } + + + + /// + public static void ClearGlobalDataInterceptor() + where T : class, IData + { + _dataFacade.ClearGlobalDataInterceptor(); + } + + + + #endregion diff --git a/Composite/Data/DataFacadeImpl.cs b/Composite/Data/DataFacadeImpl.cs index 19cd2d5c75..082a068625 100644 --- a/Composite/Data/DataFacadeImpl.cs +++ b/Composite/Data/DataFacadeImpl.cs @@ -24,6 +24,8 @@ internal class DataFacadeImpl : IDataFacade { private static readonly string LogTitle = "DataFacade"; + public Dictionary GlobalDataInterceptors = new Dictionary(); + static readonly Cache _dataBySourceIdCache = new Cache("Data by sourceId", 2000); private static readonly object _storeCreationLock = new object(); @@ -90,20 +92,30 @@ public IQueryable GetData(bool useCaching, IEnumerable providerNam resultQueryable = new List().AsQueryable(); } + + + DataInterceptor threadeddataInterceptor; + this.DataInterceptors.TryGetValue(typeof(T), out threadeddataInterceptor); + + DataInterceptor globaDataInterceptor = GlobalDataInterceptors.FirstOrDefault(f => GlobalDataInterceptors.First().Key.IsAssignableFrom(typeof(T))).Value; - DataInterceptor dataInterceptor; - this.DataInterceptors.TryGetValue(typeof(T), out dataInterceptor); - if (dataInterceptor != null) + List dataInterceptors = new List { threadeddataInterceptor, globaDataInterceptor }; + + + foreach (var dataInterceptor in dataInterceptors) { - try - { - resultQueryable = dataInterceptor.InterceptGetData(resultQueryable); - } - catch (Exception ex) + if (dataInterceptor != null) { - Log.LogError(LogTitle, "Calling data interceptor failed with the following exception"); - Log.LogError(LogTitle, ex); + try + { + resultQueryable = dataInterceptor.InterceptGetData(resultQueryable); + } + catch (Exception ex) + { + Log.LogError(LogTitle, "Calling data interceptor failed with the following exception"); + Log.LogError(LogTitle, ex); + } } } @@ -146,19 +158,28 @@ public T GetDataFromDataSourceId(DataSourceId dataSourceId, bool useCaching) { resultData = DataWrappingFacade.Wrap(resultData); } + DataInterceptor threadeddataInterceptor; - DataInterceptor dataInterceptor; - this.DataInterceptors.TryGetValue(typeof(T), out dataInterceptor); - if (dataInterceptor != null) + DataInterceptor globaDataInterceptor = GlobalDataInterceptors.FirstOrDefault(f => GlobalDataInterceptors.First().Key.IsAssignableFrom(typeof(T))).Value; + + this.DataInterceptors.TryGetValue(typeof(T), out threadeddataInterceptor); + + List dataInterceptors = new List{threadeddataInterceptor,globaDataInterceptor}; + + + foreach (var dataInterceptor in dataInterceptors) { - try + if (dataInterceptor != null) { - resultData = dataInterceptor.InterceptGetDataFromDataSourceId(resultData); - } - catch (Exception ex) - { - Log.LogError(LogTitle, "Calling data interceptor failed with the following exception"); - Log.LogError(LogTitle, ex); + try + { + resultData = dataInterceptor.InterceptGetDataFromDataSourceId(resultData); + } + catch (Exception ex) + { + Log.LogError(LogTitle, "Calling data interceptor failed with the following exception"); + Log.LogError(LogTitle, ex); + } } } @@ -196,7 +217,32 @@ public void ClearDataInterceptor() where T : class, IData } } + public void SetGlobalDataInterceptor(DataInterceptor dataInterceptor) where T : class, IData + { + if (GlobalDataInterceptors.ContainsKey(typeof(T))) throw new InvalidOperationException("A data interceptor has already been set"); + + GlobalDataInterceptors.Add(typeof(T), dataInterceptor); + + Log.LogVerbose(LogTitle, + $"Global Data interception added to the data type '{typeof (T)}' with interceptor type '{dataInterceptor.GetType()}'"); + } + + public bool HasGlobalDataInterceptor() where T : class, IData + { + return GlobalDataInterceptors.ContainsKey(typeof(T)); + } + + + + public void ClearGlobalDataInterceptor() where T : class, IData + { + if (GlobalDataInterceptors.ContainsKey(typeof(T))) + { + GlobalDataInterceptors.Remove(typeof(T)); + Log.LogVerbose(LogTitle, $"Global Data interception cleared for the data type '{typeof (T)}'"); + } + } private Dictionary DataInterceptors { @@ -218,7 +264,7 @@ private Dictionary DataInterceptors } } - + public void Update(IEnumerable dataset, bool suppressEventing, bool performForeignKeyIntegrityCheck, bool performValidation) { diff --git a/Composite/Data/IDataFacade.cs b/Composite/Data/IDataFacade.cs index 1b4c5891be..2ac3443945 100644 --- a/Composite/Data/IDataFacade.cs +++ b/Composite/Data/IDataFacade.cs @@ -53,5 +53,9 @@ internal interface IDataFacade /// /// bool ValidatePath(TFile file, string providerName, out string errorMessage) where TFile : IFile; + + void SetGlobalDataInterceptor(DataInterceptor dataInterceptor) where T : class, IData; + bool HasGlobalDataInterceptor() where T : class, IData; + void ClearGlobalDataInterceptor() where T : class, IData; } } From 28e5db879e0c44817956be25bf5762953c6aa19b Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:40:51 +0200 Subject: [PATCH 019/213] introducing default services to data service scope manager --- Composite/Data/DataScope.cs | 6 ++++++ Composite/Data/DataServiceScopeManager.cs | 24 ++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Composite/Data/DataScope.cs b/Composite/Data/DataScope.cs index 132dad39c9..d649260dfd 100644 --- a/Composite/Data/DataScope.cs +++ b/Composite/Data/DataScope.cs @@ -25,6 +25,12 @@ public void AddService(object service) DataServiceScopeManager.AddService(service); } + /// + public void AddDefaultService(object service) + { + DataServiceScopeManager.AddDefaultService(service); + } + /// public DataScope(DataScopeIdentifier dataScope) : this(dataScope, null) diff --git a/Composite/Data/DataServiceScopeManager.cs b/Composite/Data/DataServiceScopeManager.cs index 8f359cdefc..0928aa41fa 100644 --- a/Composite/Data/DataServiceScopeManager.cs +++ b/Composite/Data/DataServiceScopeManager.cs @@ -14,7 +14,21 @@ public static class DataServiceScopeManager { internal static void AddService(object service) { - DataServiceScopeStack.Peek().Add(service); + if (DataServiceScopeStack?.Peek() != null) + { + DataServiceScopeStack.Peek().Add(service); + } + else + { + var message = + "The data service stack is not pushed befor use"; + throw new InvalidOperationException(message); + } + } + + internal static void AddDefaultService(object service) + { + DataServiceDefaultList.Add(service); } internal static object GetService(Type t) @@ -27,9 +41,12 @@ internal static object GetService(Type t) return stack.Current.Last(f => f.GetType() == t); } } - return null; + + return DataServiceDefaultList.Last(f=>f.GetType()==t); } + private static readonly List DataServiceDefaultList = new List(); + private static Stack> DataServiceScopeStack { get @@ -51,7 +68,8 @@ private static Stack> DataServiceScopeStack internal static void PopDataServiceScope() { - DataServiceScopeStack.Pop(); + if(DataServiceScopeStack.Count>0) + DataServiceScopeStack.Pop(); } internal static void PushDataServiceScope() { From fa6552702c0fa561134521290de8812c8a63a07e Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:42:13 +0200 Subject: [PATCH 020/213] introducing a facade to add default services to data service scope manager --- Composite/Data/DataScopeServicesFacade.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Composite/Data/DataScopeServicesFacade.cs diff --git a/Composite/Data/DataScopeServicesFacade.cs b/Composite/Data/DataScopeServicesFacade.cs new file mode 100644 index 0000000000..6111e0bc13 --- /dev/null +++ b/Composite/Data/DataScopeServicesFacade.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Composite.Data +{ + /// + /// Facade for added services to data scope + /// + public static class DataScopeServicesFacade + { + /// + /// Adds a default service to data scope manager. All DataScopes created after this point will include the service you provide. + /// + /// + public static void RegisterDefaultService(object service) + { + DataServiceScopeManager.AddDefaultService(service); + } + } +} From 36e5d46f4b47a4fe20fd6acb15efc5e3abfb658d Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:44:12 +0200 Subject: [PATCH 021/213] using data connection to get data instead of data facade so it could use data scope services --- Composite/Data/PageManager.cs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Composite/Data/PageManager.cs b/Composite/Data/PageManager.cs index 6449e60816..e4324cce90 100644 --- a/Composite/Data/PageManager.cs +++ b/Composite/Data/PageManager.cs @@ -81,11 +81,15 @@ public static IPage GetPageById(Guid id, bool readonlyValue) } else { - result = (from page in DataFacade.GetData(false) - where page.Id == id - select page).FirstOrDefault(); + using (var con = new DataConnection()) + { + result = (from page in con.Get() + where page.Id == id + select page).FirstOrDefault(); + + _pageCache.Add(cacheKey, new ExtendedNullable { Value = result }); + } - _pageCache.Add(cacheKey, new ExtendedNullable { Value = result }); } if (result == null) @@ -162,13 +166,17 @@ public static ReadOnlyCollection GetPlaceholderContent( return cachedValue; } - var list = DataFacade.GetData(f => f.PageId == pageId).ToList(); + using (var con = new DataConnection()) + { + var list = (con.Get().Where(f => f.PageId == pageId)).ToList(); - var readonlyList = new ReadOnlyCollection(list); + var readonlyList = new ReadOnlyCollection(list); - _placeholderCache.Add(cacheKey, readonlyList); + _placeholderCache.Add(cacheKey, readonlyList); + + return readonlyList; + } - return readonlyList; } #endregion Public @@ -364,6 +372,6 @@ private static void SubscribeToEvents() DataEventSystemFacade.SubscribeToStoreChanged(OnPageStructureStoreChanged, true); } - #endregion Private + #endregion Private } } From 29e0cf5ee37659ac4e5a9661cf25ba6ca573ba9a Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:45:31 +0200 Subject: [PATCH 022/213] introducing the url to entity token interface for services --- .../IServiceUrlToEntityTokenMapper.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 Composite/C1Console/Elements/IServiceUrlToEntityTokenMapper.cs diff --git a/Composite/C1Console/Elements/IServiceUrlToEntityTokenMapper.cs b/Composite/C1Console/Elements/IServiceUrlToEntityTokenMapper.cs new file mode 100644 index 0000000000..53294f3ee9 --- /dev/null +++ b/Composite/C1Console/Elements/IServiceUrlToEntityTokenMapper.cs @@ -0,0 +1,34 @@ +using Composite.C1Console.Security; + +namespace Composite.C1Console.Elements +{ + /// + /// Allows Data Scope Services to Add or Clean their parameters to or from the URL and update a entity token with their associate properties + /// + public interface IServiceUrlToEntityTokenMapper + { + /// + /// Gets a URL and Returns the url with parametres associated with an entity token, or original URL if current entity token does not support this kind of entity token. + /// + /// The url. + /// The entity token. + /// A URL that will display the data item - this can be an "internal" URL which is later transformed by a IInternalUrlConverter. Intended for public consumption. + string TryGetUrl(ref string url, EntityToken entityToken); + + /// + /// Updates an entity token according to a url. + /// + /// The url. + /// The entity token. + /// + EntityToken TryGetEntityToken(string url, ref EntityToken entityToken); + + /// + /// Gets a url and cleans it up from its parameters. + /// + /// The url. + /// + string CleanUrl(ref string url); + + } +} From 98ee2731bf1a6d6120daf3fc1dfef1997db193ee Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:46:40 +0200 Subject: [PATCH 023/213] adding service mappers to url to entity token mapper facade --- .../Elements/UrlToEntityTokenFacade.cs | 36 ++++++++++++++++--- Composite/Composite.csproj | 2 ++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs b/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs index fb57d5401f..faf5532591 100644 --- a/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs +++ b/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Linq; using Composite.C1Console.Security; using Composite.Core; @@ -12,6 +11,7 @@ namespace Composite.C1Console.Elements public static class UrlToEntityTokenFacade { private static readonly ConcurrentBag _mappers = new ConcurrentBag(); + private static readonly ConcurrentBag _serviceMappers = new ConcurrentBag(); /// /// Returns a url associated with an entity token, or null if current entity token does not support this kind of entity token. @@ -20,7 +20,8 @@ public static class UrlToEntityTokenFacade /// URL for public consumption public static string TryGetUrl(EntityToken entityToken) { - return _mappers.Select(mapper => mapper.TryGetUrl(entityToken)).FirstOrDefault(url => url != null); + var theurl = _mappers.Select(mapper => mapper.TryGetUrl(entityToken)).FirstOrDefault(url => url != null); + return _serviceMappers.Select(f => f.TryGetUrl(ref theurl, entityToken)).Last(); } /// @@ -31,7 +32,11 @@ public static string TryGetUrl(EntityToken entityToken) /// URL for public consumption public static BrowserViewSettings TryGetBrowserViewSettings(EntityToken entityToken, bool showPublishedView) { - return _mappers.Select(mapper => mapper.TryGetBrowserViewSettings(entityToken, showPublishedView)).FirstOrDefault(settings => settings != null && settings.Url != null); + var theBrowserSetting = _mappers.Select(mapper => mapper.TryGetBrowserViewSettings(entityToken, showPublishedView)).FirstOrDefault(settings => settings?.Url != null); + if (theBrowserSetting == null) return null; + var originalUrl = theBrowserSetting.Url; + theBrowserSetting.Url = _serviceMappers.Select(f => f.TryGetUrl(ref originalUrl, entityToken)).Last(); + return theBrowserSetting; } @@ -42,7 +47,10 @@ public static BrowserViewSettings TryGetBrowserViewSettings(EntityToken entityTo /// public static EntityToken TryGetEntityToken(string url) { - return _mappers.Select(mapper => mapper.TryGetEntityToken(url)).FirstOrDefault(entityToken => entityToken != null); + var originalUrl = url; + url = _serviceMappers.Select(sm => sm.CleanUrl(ref url)).Last(); + var baseEntityToken = _mappers.Select(mapper => mapper.TryGetEntityToken(url)).FirstOrDefault(entityToken => entityToken != null); + return _serviceMappers.Select(sm => sm.TryGetEntityToken(originalUrl, ref baseEntityToken)).Last(); } /// @@ -62,5 +70,23 @@ public static void Register(IUrlToEntityTokenMapper mapper) _mappers.Add(mapper); } + + /// + /// Register an implementation of + /// + /// + public static void Register(IServiceUrlToEntityTokenMapper serviceMapper) + { + Verify.ArgumentNotNull(serviceMapper, "mapper"); + + if (_serviceMappers.Count > 100) + { + Log.LogWarning("UrlToEntityTokenFacade", "More than 100 implementations of {0}-s registered: possible memory leak. Registered type: {1}", + typeof(IServiceUrlToEntityTokenMapper).Name, serviceMapper.GetType().FullName); + return; + } + + _serviceMappers.Add(serviceMapper); + } } } diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 529f6b7ea4..faa4f5021d 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -164,6 +164,8 @@ + + From cd327b68dac477136f40ccad27c089f47cfbf540 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:52:40 +0200 Subject: [PATCH 024/213] to attached desired versioning setting from url path this http module is defined. the folowing line should be added to website's web.config --- .../VersionHttpModule.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 Composite.VersionPublishing/VersionHttpModule.cs diff --git a/Composite.VersionPublishing/VersionHttpModule.cs b/Composite.VersionPublishing/VersionHttpModule.cs new file mode 100644 index 0000000000..8d6eaf7bab --- /dev/null +++ b/Composite.VersionPublishing/VersionHttpModule.cs @@ -0,0 +1,53 @@ +using System; +using System.Web; +using Composite.Core.Extensions; +using Composite.Core.Routing.Pages; +using Composite.Data; + +namespace Composite.VersionPublishing +{ + class VersionHttpModule : IHttpModule + { + private DataConnection _dataConnection; + + private void SetVersioningServicefromUrlPath(string pathInfo) + { + if (VersionNameUrlHelper.CheckIfAdminUrl(pathInfo)) + { + _dataConnection.AddService(VersioningServiceSettings.NoFiltering()); + return; + } + var versionName = VersionNameUrlHelper.ResolveVersionName(pathInfo); + if (!versionName.IsNullOrEmpty()) + { + C1PageRoute.RegisterPathInfoUsage(); + _dataConnection.AddService(VersioningServiceSettings.ByName(versionName)); + } + } + + public void Init(HttpApplication context) + { + context.BeginRequest += context_BeginRequest; + context.EndRequest += context_EndRequest; + } + + private void context_EndRequest(object sender, EventArgs e) + { + _dataConnection.Dispose(); + } + + private void context_BeginRequest(object sender, EventArgs e) + { + var httpApplication = sender as HttpApplication; + if (httpApplication == null) return; + + _dataConnection = new DataConnection(); + var httpContext = httpApplication.Context; + SetVersioningServicefromUrlPath(httpContext.Request.Path); + } + + public void Dispose() + { + } + } +} From a29ecceadead716f3e6ee471b2bb135f0d55d503 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:54:31 +0200 Subject: [PATCH 025/213] versioning service's url to entity token mapper --- .../VersionedDataUrlToEntityTokenMapper.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs diff --git a/Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs b/Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs new file mode 100644 index 0000000000..af49ad49ee --- /dev/null +++ b/Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs @@ -0,0 +1,64 @@ +using System.Linq; +using Composite.C1Console.Elements; +using Composite.C1Console.Security; +using Composite.Core; +using Composite.Core.Extensions; +using Composite.Data; +using Composite.Data.Types; + +namespace Composite.VersionPublishing +{ + class VersionedDataUrlToEntityTokenMapper : IServiceUrlToEntityTokenMapper + { + public string TryGetUrl(ref string url,EntityToken entityToken) + { + var dataEntityToken = entityToken as DataEntityToken; + + var data = dataEntityToken?.Data; + + var page = data as IPage; + if (page != null) + { + if (!page.VersionName.IsNullOrEmpty()) + { + var biultUrl = new UrlBuilder(url); + url = biultUrl.FilePath + VersionNameUrlHelper.VersionNameToUrl(page.VersionName) + + biultUrl.QueryString; + } + + } + return url; + + } + + public EntityToken TryGetEntityToken(string url, ref EntityToken entityToken) + { + var versionname = VersionNameUrlHelper.ResolveVersionName(url); + var page = (entityToken as DataEntityToken)?.Data as IPage; + if (page != null) + { + using (var dc = new DataConnection()) + { + dc.AddService(VersioningServiceSettings.ByName(versionname)); + var firstOrDefault = dc.Get() + .FirstOrDefault(f => f.Id == page.Id); + if (firstOrDefault != null) + { + var versionId = firstOrDefault.VersionId; + page.VersionId = versionId; + page.VersionName = versionname; + entityToken = page.GetDataEntityToken(); + return entityToken; + } + } + } + return entityToken; + } + + public string CleanUrl(ref string url) + { + return url.Replace(@"/"+ VersionNameUrlHelper.ResolveVersionName(url), ""); + } + + } +} From d7c787eb9df669c87e7630eef3167118e6456bd6 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:55:30 +0200 Subject: [PATCH 026/213] a helper class to deal with url strings --- .../VersionNameUrlHelper.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Composite.VersionPublishing/VersionNameUrlHelper.cs diff --git a/Composite.VersionPublishing/VersionNameUrlHelper.cs b/Composite.VersionPublishing/VersionNameUrlHelper.cs new file mode 100644 index 0000000000..ec9b7bb637 --- /dev/null +++ b/Composite.VersionPublishing/VersionNameUrlHelper.cs @@ -0,0 +1,52 @@ +using System; +using System.Text.RegularExpressions; +using Composite.Core.Extensions; +using Composite.Core.WebClient; + +namespace Composite.VersionPublishing +{ + public static class VersionNameUrlHelper + { + const string Pattern = @"\bc1version\b\((.*?)\)"; + private static readonly Regex CompiledRegex = new Regex(Pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + public static string ResolveVersionName(string pathInfo) + { + if (pathInfo.IsNullOrEmpty()) + return null; + var matches = CompiledRegex.Matches(pathInfo); + if (matches.Count == 1) + { + var versionString = matches[0].Value; + return versionString.Substring(10, versionString.Length - 11); + } + return null; + } + + public static bool CheckIfAdminUrl(string pathInfo) + { + if (pathInfo.IsNullOrEmpty()) + return false; + var adminRootPath = UrlUtils.AdminRootPath.ToLowerInvariant(); + + if (!adminRootPath.EndsWith("/")) + { + adminRootPath = $"{adminRootPath}/"; + } + + string currentPath = pathInfo.ToLowerInvariant(); + if (currentPath.StartsWith(adminRootPath)) + { + return true; + } + return false; + } + + public static string VersionNameToUrl(string versionName) + { + return @"/" + @"c1version(" + Uri.EscapeDataString(versionName) + ")"; + } + + + } +} \ No newline at end of file From 036a5ad5de5129de680ab5779506c547c022b04f Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:57:46 +0200 Subject: [PATCH 027/213] change in data interceptor registration to 1.put it into global interceptors 2.make it work with other versioned datas --- .../VersioningServiceSettings.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Composite.VersionPublishing/VersioningServiceSettings.cs b/Composite.VersionPublishing/VersioningServiceSettings.cs index a5381c2c1a..47f94bd28d 100644 --- a/Composite.VersionPublishing/VersioningServiceSettings.cs +++ b/Composite.VersionPublishing/VersioningServiceSettings.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Composite.Data; using Composite.Data.Types; @@ -45,8 +41,8 @@ public static VersioningServiceSettings ByName(string versionName) private VersioningServiceSettings(string versionName) { - if (!DataFacade.HasDataInterceptor())//TODO: should I care - DataFacade.SetDataInterceptor(new PageVersionFilteringDataInterceptor()); + if (!DataFacade.HasGlobalDataInterceptor()) + DataFacade.SetGlobalDataInterceptor(new PageVersionFilteringDataInterceptor()); _versionFilteringSettings = new VersionFilteringSettings { @@ -57,8 +53,8 @@ private VersioningServiceSettings(string versionName) private VersioningServiceSettings(VersionFilteringMode filteringMode, DateTime time) { - if(!DataFacade.HasDataInterceptor())//TODO: should I care - DataFacade.SetDataInterceptor(new PageVersionFilteringDataInterceptor()); + if(!DataFacade.HasGlobalDataInterceptor()) + DataFacade.SetGlobalDataInterceptor(new PageVersionFilteringDataInterceptor()); _versionFilteringSettings = new VersionFilteringSettings { From 6315392ebba31337f7d2119b9ad227a30180a4a0 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 10:59:24 +0200 Subject: [PATCH 028/213] introducing versioning service's facade to add default service --- .../VersionPublishingFacade.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Composite.VersionPublishing/VersionPublishingFacade.cs diff --git a/Composite.VersionPublishing/VersionPublishingFacade.cs b/Composite.VersionPublishing/VersionPublishingFacade.cs new file mode 100644 index 0000000000..37198b2db8 --- /dev/null +++ b/Composite.VersionPublishing/VersionPublishingFacade.cs @@ -0,0 +1,12 @@ +using Composite.Data; + +namespace Composite.VersionPublishing +{ + public static class VersionPublishingFacade + { + public static void RegisterDefaultVersioningService(VersioningServiceSettings versioningServiceSettings) + { + DataScopeServicesFacade.RegisterDefaultService(versioningServiceSettings); + } + } +} From cdf1fa382e325bb0f234d4ca9492b3f7b3ba2188 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 11:00:17 +0200 Subject: [PATCH 029/213] registering service mapper and setting default service --- Composite.VersionPublishing/StartupHandler.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Composite.VersionPublishing/StartupHandler.cs b/Composite.VersionPublishing/StartupHandler.cs index d73f1253eb..a2e532dd13 100644 --- a/Composite.VersionPublishing/StartupHandler.cs +++ b/Composite.VersionPublishing/StartupHandler.cs @@ -1,11 +1,15 @@ -using System; +using Composite.C1Console.Elements; +using Composite.Core.Application; namespace Composite.VersionPublishing { + [ApplicationStartup] public class StartupHandler { public static void OnBeforeInitialize() { + VersionPublishingFacade.RegisterDefaultVersioningService(VersioningServiceSettings.MostRelevant()); + UrlToEntityTokenFacade.Register(new VersionedDataUrlToEntityTokenMapper()); } public static void OnInitialized() From 1a3c87b8382460488d9ac1b055282ffc940391d7 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Wed, 11 May 2016 11:01:45 +0200 Subject: [PATCH 030/213] change in data interceptor in order to work with other versioned data --- .../Composite.VersionPublishing.csproj | 5 +++ .../IVersionFilteringSettings.cs | 1 - .../VersionFilteringDataInterceptor.cs | 37 +++++++++++++++++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/Composite.VersionPublishing/Composite.VersionPublishing.csproj b/Composite.VersionPublishing/Composite.VersionPublishing.csproj index 4c1b92200f..66c07c832d 100644 --- a/Composite.VersionPublishing/Composite.VersionPublishing.csproj +++ b/Composite.VersionPublishing/Composite.VersionPublishing.csproj @@ -33,6 +33,7 @@ + @@ -44,8 +45,12 @@ + + + + diff --git a/Composite.VersionPublishing/IVersionFilteringSettings.cs b/Composite.VersionPublishing/IVersionFilteringSettings.cs index c2c1e60210..02f8c63c41 100644 --- a/Composite.VersionPublishing/IVersionFilteringSettings.cs +++ b/Composite.VersionPublishing/IVersionFilteringSettings.cs @@ -1,5 +1,4 @@ using System; -using Composite.Data; namespace Composite.VersionPublishing { diff --git a/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs b/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs index db51f2c016..5cf971fddf 100644 --- a/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs +++ b/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs @@ -14,7 +14,8 @@ public override IQueryable InterceptGetData(IQueryable query) return FilterPages((IQueryable) query) as IQueryable; } - return query; + return FilterOtherVersionedPages(query); + } private static IQueryable FilterPages(IQueryable query) @@ -32,7 +33,8 @@ private static IQueryable FilterPages(IQueryable query) Where( page => page.VersionName == vfs.VersionName || - (page.PublishTime <= DateTime.Now && page.UnpublishTime >= DateTime.Now)) + ((page.PublishTime ?? DateTime.MinValue) <= DateTime.Now && + (page.UnpublishTime ?? DateTime.MaxValue) >= DateTime.Now)) .GroupBy(p => p.Id) .Select( g => @@ -49,13 +51,42 @@ private static IQueryable FilterPages(IQueryable query) .GroupBy(p => p.Id) .Select(g => g.OrderByDescending(page => page.PublishTime ?? DateTime.MinValue).First()); case VersionFilteringMode.None: - return query; + return query.OrderByDescending(page => page.PublishTime ?? DateTime.MinValue); case VersionFilteringMode.Relevant: return query .GroupBy(p => p.Id) .Select(g => g.OrderByDescending(page => page.PublishTime ?? DateTime.MinValue).First()); } + return query; + } + } + + private static IQueryable FilterOtherVersionedPages(IQueryable query) + { + using (DataConnection dataConnection = new DataConnection()) + { + var setting = dataConnection.GetService(typeof(VersioningServiceSettings)); + if ((setting as VersioningServiceSettings) == null) + return query; + var vfs = (setting as VersioningServiceSettings).VersionFilteringSettings; + + + //var relatedPages = dataConnection.Get().Where(f => f.VersionName == vfs.VersionName); + var relatedPages = FilterPages(dataConnection.Get()); + + if (relatedPages != null) + { + var a = (from versioned in query + join page in relatedPages on (versioned as IVersioned).VersionId equals page.VersionId + select versioned).Union(from versioned in query + where (versioned as IVersioned).VersionId == Guid.Empty + select versioned); + return a; + } + + + return query; } } From 0b7991b612e4d7dcfaf2d5dba60d27615d8cd446 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Thu, 19 May 2016 14:06:42 +0300 Subject: [PATCH 031/213] Update styles and code for version publishing --- .../views/browser/BrowserPageBinding.js | 104 +++++++++--------- .../content/views/browser/browser.css | 2 +- .../styles/default/toolbars/nav-toolbar.less | 19 +++- 3 files changed, 71 insertions(+), 54 deletions(-) diff --git a/Website/Composite/content/views/browser/BrowserPageBinding.js b/Website/Composite/content/views/browser/BrowserPageBinding.js index a3356310eb..51cbe4c594 100644 --- a/Website/Composite/content/views/browser/BrowserPageBinding.js +++ b/Website/Composite/content/views/browser/BrowserPageBinding.js @@ -139,68 +139,23 @@ BrowserPageBinding.prototype.handleBroadcast = function (broadcast, arg) { if (arg.syncHandle == this.getSyncHandle() && !(arg.source instanceof GenericViewBinding) && arg.actionProfile) { var self = this; + var bundleselector = this.getBundleSelector(); //TODO move this - if (!this.shadowTree.bundleselector) { - var addressbar = this.bindingWindow.bindingMap.addressbar; - var selector = SelectorBinding.newInstance(this.bindingDocument); - addressbar.bindingElement.parentNode.appendChild(selector.bindingElement); - selector.attach(); - this.shadowTree.bundleselector = selector; - - //TODO Stub for CSS move to style - selector.bindingElement.style.position = "absolute"; - selector.bindingElement.style.right = "0"; - selector.bindingElement.parentNode.style.position = "relative"; - - - - selector.addActionListener(SelectorBinding.ACTION_SELECTIONCHANGED, { - handleAction: (function(action) { - var binding = action.target; - - switch (action.type) { - case SelectorBinding.ACTION_SELECTIONCHANGED: - var bundleValue = binding.getValue(); - var selectedTreeNode = this.getSystemTree().getFocusedTreeNodeBindings().getFirst(); - var selectedBundleNode = null; - - selectedTreeNode.nodes.each(function(node) { - if (bundleValue == node.getHandle()) { - selectedBundleNode = node; - return false; - } - return true; - }, this); - - if (selectedBundleNode) { - selectedTreeNode.node = selectedBundleNode; - selectedTreeNode.isDisabled = selectedBundleNode.isDisabled(); - selectedTreeNode.setLabel(selectedBundleNode.getLabel()); - selectedTreeNode.setToolTip(selectedBundleNode.getToolTip()); - selectedTreeNode.setImage ( selectedTreeNode.computeImage ()); - } - - this.getSystemTree().focusSingleTreeNodeBinding(selectedTreeNode); - break; - } - }).bind(this) - }); - - } - var selectedTreeNode = this.getSystemTree().getFocusedTreeNodeBindings().getFirst(); if (selectedTreeNode.nodes && selectedTreeNode.nodes.getLength() > 1) { var list = new List(); selectedTreeNode.nodes.each(function(node) { list.add(new SelectorBindingSelection(node.getLabel(), node.getHandle(), node.getHandle() === selectedTreeNode.node.getHandle())); }); - this.shadowTree.bundleselector.populateFromList(list); - this.shadowTree.bundleselector.show(); + bundleselector.populateFromList(list); + bundleselector.show(); } else { - this.shadowTree.bundleselector.hide(); + bundleselector.hide(); } + this.bindingWindow.bindingMap.navbar.flex(); + //TODO end move this @@ -1277,6 +1232,53 @@ BrowserPageBinding.prototype.getState = function () { } +BrowserPageBinding.prototype.getBundleSelector = function () { + + if (!this.shadowTree.bundleselector) { + var addressrightgroup = this.bindingWindow.bindingMap.addressrightgroup; + var selector = SelectorBinding.newInstance(this.bindingDocument); + addressrightgroup.bindingElement.insertBefore(selector.bindingElement, addressrightgroup.bindingElement.firstChild); + selector.attach(); + this.shadowTree.bundleselector = selector; + + selector.addActionListener(SelectorBinding.ACTION_SELECTIONCHANGED, { + handleAction: (function (action) { + var binding = action.target; + + switch (action.type) { + case SelectorBinding.ACTION_SELECTIONCHANGED: + var bundleValue = binding.getValue(); + var selectedTreeNode = this.getSystemTree().getFocusedTreeNodeBindings().getFirst(); + var selectedBundleNode = null; + + selectedTreeNode.nodes.each(function (node) { + if (bundleValue == node.getHandle()) { + selectedBundleNode = node; + return false; + } + return true; + }, this); + + if (selectedBundleNode) { + selectedTreeNode.node = selectedBundleNode; + selectedTreeNode.isDisabled = selectedBundleNode.isDisabled(); + selectedTreeNode.setLabel(selectedBundleNode.getLabel()); + selectedTreeNode.setToolTip(selectedBundleNode.getToolTip()); + selectedTreeNode.setImage(selectedTreeNode.computeImage()); + } + + this.getSystemTree().focusSingleTreeNodeBinding(selectedTreeNode); + break; + } + }).bind(this) + }); + + } + + return this.shadowTree.bundleselector; +} + + /** * Auto expand tree */ diff --git a/Website/Composite/content/views/browser/browser.css b/Website/Composite/content/views/browser/browser.css index f503506895..8ea3b46b30 100644 --- a/Website/Composite/content/views/browser/browser.css +++ b/Website/Composite/content/views/browser/browser.css @@ -4,7 +4,7 @@ #addressbar { float: left; margin-top: 2px; - padding: 0; + padding: 0 0 0 10px; } diff --git a/Website/Composite/styles/default/toolbars/nav-toolbar.less b/Website/Composite/styles/default/toolbars/nav-toolbar.less index 3eac1432a2..c364ee99e6 100644 --- a/Website/Composite/styles/default/toolbars/nav-toolbar.less +++ b/Website/Composite/styles/default/toolbars/nav-toolbar.less @@ -1,10 +1,25 @@ -.nav-toolbar { +ui|toolbar.nav-toolbar { border-top: solid 1px @base-border-color; ui|toolbarbody { + &.alignright { + ui|toolbarbutton { - margin-left: 2px; + margin-left: 8px; + margin-right: 2px; + } + + ui|selector { + margin: 0 0 0 6px; + + ui|clickbutton { + margin: 0; + + > ui|labelbox{ + padding: 7px 30px 8px 11px; + } + } } } } From bacb1dd355e100493bea5361a7f97a6bb710e7f2 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 19 May 2016 15:54:19 +0200 Subject: [PATCH 032/213] Refactoring --- .../VersionFilteringDataInterceptor.cs | 49 +++++++----- .../VersionHttpModule.cs | 4 +- .../VersionNameUrlHelper.cs | 27 +++---- .../VersionedDataUrlToEntityTokenMapper.cs | 8 +- .../VersioningServiceSettings.cs | 14 +++- .../IServiceUrlToEntityTokenMapper.cs | 3 +- .../Elements/UrlToEntityTokenFacade.cs | 40 +++++++--- Composite/Data/DataFacadeImpl.cs | 80 ++++++++----------- Composite/Data/DataServiceScopeManager.cs | 30 +++---- 9 files changed, 134 insertions(+), 121 deletions(-) diff --git a/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs b/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs index 5cf971fddf..70f1e1bad4 100644 --- a/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs +++ b/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs @@ -20,12 +20,17 @@ public override IQueryable InterceptGetData(IQueryable query) private static IQueryable FilterPages(IQueryable query) { - using (DataConnection dataConnection = new DataConnection()) + using (var dataConnection = new DataConnection()) { - var setting = dataConnection.GetService(typeof (VersioningServiceSettings)); - if ((setting as VersioningServiceSettings) == null) + var settings = dataConnection.GetService(typeof (VersioningServiceSettings)) + as VersioningServiceSettings; + + if (settings == null) + { return query; - var vfs = (setting as VersioningServiceSettings).VersionFilteringSettings; + } + + var vfs = settings.VersionFilteringSettings; if (vfs.VersionName != null) { @@ -64,28 +69,30 @@ private static IQueryable FilterPages(IQueryable query) private static IQueryable FilterOtherVersionedPages(IQueryable query) { - using (DataConnection dataConnection = new DataConnection()) + using (var dataConnection = new DataConnection()) { - var setting = dataConnection.GetService(typeof(VersioningServiceSettings)); - if ((setting as VersioningServiceSettings) == null) + var settings = dataConnection.GetService(typeof(VersioningServiceSettings)) + as VersioningServiceSettings; + + if (settings == null) + { return query; - var vfs = (setting as VersioningServiceSettings).VersionFilteringSettings; + } + + //var vfs = settings.VersionFilteringSettings; + //var relatedPages = dataConnection.Get().Where(f => f.VersionName == vfs.VersionName); - - //var relatedPages = dataConnection.Get().Where(f => f.VersionName == vfs.VersionName); - var relatedPages = FilterPages(dataConnection.Get()); + var relatedPages = FilterPages(dataConnection.Get()); - if (relatedPages != null) - { - var a = (from versioned in query + if (relatedPages != null) + { + return (from versioned in query join page in relatedPages on (versioned as IVersioned).VersionId equals page.VersionId - select versioned).Union(from versioned in query - where (versioned as IVersioned).VersionId == Guid.Empty - select versioned); - return a; - } - - + select versioned) + .Union(from versioned in query + where (versioned as IVersioned).VersionId == Guid.Empty + select versioned); + } return query; } diff --git a/Composite.VersionPublishing/VersionHttpModule.cs b/Composite.VersionPublishing/VersionHttpModule.cs index 8d6eaf7bab..5e21298866 100644 --- a/Composite.VersionPublishing/VersionHttpModule.cs +++ b/Composite.VersionPublishing/VersionHttpModule.cs @@ -10,7 +10,7 @@ class VersionHttpModule : IHttpModule { private DataConnection _dataConnection; - private void SetVersioningServicefromUrlPath(string pathInfo) + private void SetVersioningServiceFromUrlPath(string pathInfo) { if (VersionNameUrlHelper.CheckIfAdminUrl(pathInfo)) { @@ -43,7 +43,7 @@ private void context_BeginRequest(object sender, EventArgs e) _dataConnection = new DataConnection(); var httpContext = httpApplication.Context; - SetVersioningServicefromUrlPath(httpContext.Request.Path); + SetVersioningServiceFromUrlPath(httpContext.Request.Path); } public void Dispose() diff --git a/Composite.VersionPublishing/VersionNameUrlHelper.cs b/Composite.VersionPublishing/VersionNameUrlHelper.cs index ec9b7bb637..05f51f52fd 100644 --- a/Composite.VersionPublishing/VersionNameUrlHelper.cs +++ b/Composite.VersionPublishing/VersionNameUrlHelper.cs @@ -13,40 +13,39 @@ public static class VersionNameUrlHelper public static string ResolveVersionName(string pathInfo) { if (pathInfo.IsNullOrEmpty()) + { return null; + } + var matches = CompiledRegex.Matches(pathInfo); - if (matches.Count == 1) + if (matches.Count != 1) { - var versionString = matches[0].Value; - return versionString.Substring(10, versionString.Length - 11); + return null; } - return null; + + var versionString = matches[0].Value; + return versionString.Substring(10, versionString.Length - 11); } public static bool CheckIfAdminUrl(string pathInfo) { if (pathInfo.IsNullOrEmpty()) + { return false; - var adminRootPath = UrlUtils.AdminRootPath.ToLowerInvariant(); + } + var adminRootPath = UrlUtils.AdminRootPath; if (!adminRootPath.EndsWith("/")) { adminRootPath = $"{adminRootPath}/"; } - string currentPath = pathInfo.ToLowerInvariant(); - if (currentPath.StartsWith(adminRootPath)) - { - return true; - } - return false; + return pathInfo.StartsWith(adminRootPath, StringComparison.InvariantCultureIgnoreCase); } public static string VersionNameToUrl(string versionName) { - return @"/" + @"c1version(" + Uri.EscapeDataString(versionName) + ")"; + return $"/c1version({Uri.EscapeDataString(versionName)})"; } - - } } \ No newline at end of file diff --git a/Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs b/Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs index af49ad49ee..48e04fa4e8 100644 --- a/Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs +++ b/Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs @@ -10,7 +10,7 @@ namespace Composite.VersionPublishing { class VersionedDataUrlToEntityTokenMapper : IServiceUrlToEntityTokenMapper { - public string TryGetUrl(ref string url,EntityToken entityToken) + public string ProcessUrl(string url, EntityToken entityToken) { var dataEntityToken = entityToken as DataEntityToken; @@ -21,9 +21,9 @@ public string TryGetUrl(ref string url,EntityToken entityToken) { if (!page.VersionName.IsNullOrEmpty()) { - var biultUrl = new UrlBuilder(url); - url = biultUrl.FilePath + VersionNameUrlHelper.VersionNameToUrl(page.VersionName) + - biultUrl.QueryString; + var urlBuilder = new UrlBuilder(url); + url = urlBuilder.FilePath + VersionNameUrlHelper.VersionNameToUrl(page.VersionName) + + urlBuilder.QueryString; } } diff --git a/Composite.VersionPublishing/VersioningServiceSettings.cs b/Composite.VersionPublishing/VersioningServiceSettings.cs index 47f94bd28d..669c00480f 100644 --- a/Composite.VersionPublishing/VersioningServiceSettings.cs +++ b/Composite.VersionPublishing/VersioningServiceSettings.cs @@ -41,8 +41,7 @@ public static VersioningServiceSettings ByName(string versionName) private VersioningServiceSettings(string versionName) { - if (!DataFacade.HasGlobalDataInterceptor()) - DataFacade.SetGlobalDataInterceptor(new PageVersionFilteringDataInterceptor()); + SetupDataInterceptor(); _versionFilteringSettings = new VersionFilteringSettings { @@ -53,8 +52,7 @@ private VersioningServiceSettings(string versionName) private VersioningServiceSettings(VersionFilteringMode filteringMode, DateTime time) { - if(!DataFacade.HasGlobalDataInterceptor()) - DataFacade.SetGlobalDataInterceptor(new PageVersionFilteringDataInterceptor()); + SetupDataInterceptor(); _versionFilteringSettings = new VersionFilteringSettings { @@ -63,6 +61,14 @@ private VersioningServiceSettings(VersionFilteringMode filteringMode, DateTime t }; } + private void SetupDataInterceptor() + { + if (!DataFacade.HasGlobalDataInterceptor()) + { + DataFacade.SetGlobalDataInterceptor(new PageVersionFilteringDataInterceptor()); + } + } + public void ChangeProperties(VersionFilteringMode filteringMode, DateTime? time) { _versionFilteringSettings.FilteringMode = filteringMode; diff --git a/Composite/C1Console/Elements/IServiceUrlToEntityTokenMapper.cs b/Composite/C1Console/Elements/IServiceUrlToEntityTokenMapper.cs index 53294f3ee9..2f2c93c3da 100644 --- a/Composite/C1Console/Elements/IServiceUrlToEntityTokenMapper.cs +++ b/Composite/C1Console/Elements/IServiceUrlToEntityTokenMapper.cs @@ -13,7 +13,7 @@ public interface IServiceUrlToEntityTokenMapper /// The url. /// The entity token. /// A URL that will display the data item - this can be an "internal" URL which is later transformed by a IInternalUrlConverter. Intended for public consumption. - string TryGetUrl(ref string url, EntityToken entityToken); + string ProcessUrl(string url, EntityToken entityToken); /// /// Updates an entity token according to a url. @@ -29,6 +29,5 @@ public interface IServiceUrlToEntityTokenMapper /// The url. /// string CleanUrl(ref string url); - } } diff --git a/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs b/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs index faf5532591..089b22f973 100644 --- a/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs +++ b/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs @@ -2,6 +2,7 @@ using System.Linq; using Composite.C1Console.Security; using Composite.Core; +using Composite.Core.Extensions; namespace Composite.C1Console.Elements { @@ -10,6 +11,8 @@ namespace Composite.C1Console.Elements /// public static class UrlToEntityTokenFacade { + private const string LogTitle = nameof(UrlToEntityTokenFacade); + private static readonly ConcurrentBag _mappers = new ConcurrentBag(); private static readonly ConcurrentBag _serviceMappers = new ConcurrentBag(); @@ -20,10 +23,12 @@ public static class UrlToEntityTokenFacade /// URL for public consumption public static string TryGetUrl(EntityToken entityToken) { - var theurl = _mappers.Select(mapper => mapper.TryGetUrl(entityToken)).FirstOrDefault(url => url != null); - return _serviceMappers.Select(f => f.TryGetUrl(ref theurl, entityToken)).Last(); + var theUrl = _mappers.Select(mapper => mapper.TryGetUrl(entityToken)).FirstOrDefault(url => url != null); + + return ProcessUrlWithServiceMappers(theUrl, entityToken); } + /// /// Returns a url / tooling settings associated with an entity token to be used in the C1 Console browser, or null if current entity token does not support this kind of entity token. /// @@ -32,14 +37,29 @@ public static string TryGetUrl(EntityToken entityToken) /// URL for public consumption public static BrowserViewSettings TryGetBrowserViewSettings(EntityToken entityToken, bool showPublishedView) { - var theBrowserSetting = _mappers.Select(mapper => mapper.TryGetBrowserViewSettings(entityToken, showPublishedView)).FirstOrDefault(settings => settings?.Url != null); + var theBrowserSetting = _mappers.Select(mapper => + mapper.TryGetBrowserViewSettings(entityToken, showPublishedView)) + .FirstOrDefault(settings => settings?.Url != null); + if (theBrowserSetting == null) return null; + var originalUrl = theBrowserSetting.Url; - theBrowserSetting.Url = _serviceMappers.Select(f => f.TryGetUrl(ref originalUrl, entityToken)).Last(); + theBrowserSetting.Url = ProcessUrlWithServiceMappers(originalUrl, entityToken); + return theBrowserSetting; } + private static string ProcessUrlWithServiceMappers(string url, EntityToken entityToken) + { + _serviceMappers.ForEach(service => + { + url = service.ProcessUrl(url, entityToken); + }); + + return url; + } + /// /// Returns an entity token associated with a url, or null if current does not support this kind of entity token. /// @@ -59,12 +79,12 @@ public static EntityToken TryGetEntityToken(string url) /// public static void Register(IUrlToEntityTokenMapper mapper) { - Verify.ArgumentNotNull(mapper, "mapper"); + Verify.ArgumentNotNull(mapper, nameof(mapper)); if (_mappers.Count > 100) { - Log.LogWarning("UrlToEntityTokenFacade", "More than 100 implementations of {0}-s registered: possible memory leak. Registered type: {1}", - typeof(IUrlToEntityTokenMapper).Name, mapper.GetType().FullName); + Log.LogWarning(LogTitle, "More than 100 implementations of {0}-s registered: possible memory leak. Registered type: {1}", + nameof(IUrlToEntityTokenMapper), mapper.GetType().FullName); return; } @@ -77,12 +97,12 @@ public static void Register(IUrlToEntityTokenMapper mapper) /// public static void Register(IServiceUrlToEntityTokenMapper serviceMapper) { - Verify.ArgumentNotNull(serviceMapper, "mapper"); + Verify.ArgumentNotNull(serviceMapper, nameof(serviceMapper)); if (_serviceMappers.Count > 100) { - Log.LogWarning("UrlToEntityTokenFacade", "More than 100 implementations of {0}-s registered: possible memory leak. Registered type: {1}", - typeof(IServiceUrlToEntityTokenMapper).Name, serviceMapper.GetType().FullName); + Log.LogWarning(LogTitle, "More than 100 implementations of {0}-s registered: possible memory leak. Registered type: {1}", + nameof(IServiceUrlToEntityTokenMapper), serviceMapper.GetType().FullName); return; } diff --git a/Composite/Data/DataFacadeImpl.cs b/Composite/Data/DataFacadeImpl.cs index 082a068625..aa289bf9e3 100644 --- a/Composite/Data/DataFacadeImpl.cs +++ b/Composite/Data/DataFacadeImpl.cs @@ -24,7 +24,7 @@ internal class DataFacadeImpl : IDataFacade { private static readonly string LogTitle = "DataFacade"; - public Dictionary GlobalDataInterceptors = new Dictionary(); + internal Dictionary GlobalDataInterceptors = new Dictionary(); static readonly Cache _dataBySourceIdCache = new Cache("Data by sourceId", 2000); private static readonly object _storeCreationLock = new object(); @@ -86,36 +86,22 @@ public IQueryable GetData(bool useCaching, IEnumerable providerNam if (!typeof(T).GetCustomInterfaceAttributes().Any()) { - throw new ArgumentException(string.Format("The given interface type ({0}) is not supported by any data providers", typeof(T))); + throw new ArgumentException($"The given interface type ({typeof (T)}) is not supported by any data providers"); } resultQueryable = new List().AsQueryable(); } - - DataInterceptor threadeddataInterceptor; - this.DataInterceptors.TryGetValue(typeof(T), out threadeddataInterceptor); - - DataInterceptor globaDataInterceptor = GlobalDataInterceptors.FirstOrDefault(f => GlobalDataInterceptors.First().Key.IsAssignableFrom(typeof(T))).Value; - - - List dataInterceptors = new List { threadeddataInterceptor, globaDataInterceptor }; - - - foreach (var dataInterceptor in dataInterceptors) + foreach (var dataInterceptor in GetDataInterceptors(typeof(T))) { - if (dataInterceptor != null) + try { - try - { - resultQueryable = dataInterceptor.InterceptGetData(resultQueryable); - } - catch (Exception ex) - { - Log.LogError(LogTitle, "Calling data interceptor failed with the following exception"); - Log.LogError(LogTitle, ex); - } + resultQueryable = dataInterceptor.InterceptGetData(resultQueryable); + } + catch (Exception ex) + { + Log.LogError(LogTitle, ex); } } @@ -124,12 +110,10 @@ public IQueryable GetData(bool useCaching, IEnumerable providerNam - - public T GetDataFromDataSourceId(DataSourceId dataSourceId, bool useCaching) where T : class, IData { - if (null == dataSourceId) throw new ArgumentNullException("dataSourceId"); + Verify.ArgumentNotNull(dataSourceId, nameof(dataSourceId)); useCaching = useCaching && DataCachingFacade.IsTypeCacheable(typeof(T)); @@ -158,28 +142,16 @@ public T GetDataFromDataSourceId(DataSourceId dataSourceId, bool useCaching) { resultData = DataWrappingFacade.Wrap(resultData); } - DataInterceptor threadeddataInterceptor; - - DataInterceptor globaDataInterceptor = GlobalDataInterceptors.FirstOrDefault(f => GlobalDataInterceptors.First().Key.IsAssignableFrom(typeof(T))).Value; - - this.DataInterceptors.TryGetValue(typeof(T), out threadeddataInterceptor); - - List dataInterceptors = new List{threadeddataInterceptor,globaDataInterceptor}; - - foreach (var dataInterceptor in dataInterceptors) + foreach (var dataInterceptor in GetDataInterceptors(typeof(T))) { - if (dataInterceptor != null) + try { - try - { - resultData = dataInterceptor.InterceptGetDataFromDataSourceId(resultData); - } - catch (Exception ex) - { - Log.LogError(LogTitle, "Calling data interceptor failed with the following exception"); - Log.LogError(LogTitle, ex); - } + resultData = dataInterceptor.InterceptGetDataFromDataSourceId(resultData); + } + catch (Exception ex) + { + Log.LogError(LogTitle, ex); } } @@ -188,6 +160,20 @@ public T GetDataFromDataSourceId(DataSourceId dataSourceId, bool useCaching) } + private IEnumerable GetDataInterceptors(Type dataType) + { + DataInterceptor threadedDataInterceptor; + + DataInterceptor globaDataInterceptor = GlobalDataInterceptors + .FirstOrDefault(f => GlobalDataInterceptors.First().Key.IsAssignableFrom(dataType)).Value; + + this.DataInterceptors.TryGetValue(dataType, out threadedDataInterceptor); + + var dataInterceptors = new List { threadedDataInterceptor, globaDataInterceptor }; + + return dataInterceptors.Where(d => d != null); + } + public void SetDataInterceptor(DataInterceptor dataInterceptor) where T : class, IData { @@ -195,7 +181,7 @@ public void SetDataInterceptor(DataInterceptor dataInterceptor) where T : cla this.DataInterceptors.Add(typeof(T), dataInterceptor); - Log.LogVerbose(LogTitle, string.Format("Data interception added to the data type '{0}' with interceptor type '{1}'", typeof(T), dataInterceptor.GetType())); + Log.LogVerbose(LogTitle, $"Data interception added to the data type '{typeof (T)}' with interceptor type '{dataInterceptor.GetType()}'"); } @@ -213,7 +199,7 @@ public void ClearDataInterceptor() where T : class, IData { this.DataInterceptors.Remove(typeof(T)); - Log.LogVerbose(LogTitle, string.Format("Data interception cleared for the data type '{0}'", typeof(T))); + Log.LogVerbose(LogTitle, $"Data interception cleared for the data type '{typeof (T)}'"); } } diff --git a/Composite/Data/DataServiceScopeManager.cs b/Composite/Data/DataServiceScopeManager.cs index 0928aa41fa..47ea65e4d1 100644 --- a/Composite/Data/DataServiceScopeManager.cs +++ b/Composite/Data/DataServiceScopeManager.cs @@ -14,16 +14,10 @@ public static class DataServiceScopeManager { internal static void AddService(object service) { - if (DataServiceScopeStack?.Peek() != null) - { - DataServiceScopeStack.Peek().Add(service); - } - else - { - var message = - "The data service stack is not pushed befor use"; - throw new InvalidOperationException(message); - } + var serviceStack = DataServiceScopeStack?.Peek(); + Verify.IsNotNull(serviceStack, "The data service stack was not pushed before use"); + + serviceStack.Add(service); } internal static void AddDefaultService(object service) @@ -33,16 +27,16 @@ internal static void AddDefaultService(object service) internal static object GetService(Type t) { - var stack = DataServiceScopeStack.GetEnumerator(); - while (stack.MoveNext()) + foreach(var serviceList in DataServiceScopeStack) { - if (stack.Current?.Find(f => f.GetType() == t) != null) + var match = serviceList?.FindLast(f => f.GetType() == t); + if (match != null) { - return stack.Current.Last(f => f.GetType() == t); + return match; } } - return DataServiceDefaultList.Last(f=>f.GetType()==t); + return DataServiceDefaultList.Last(f => f.GetType() == t); } private static readonly List DataServiceDefaultList = new List(); @@ -68,9 +62,11 @@ private static Stack> DataServiceScopeStack internal static void PopDataServiceScope() { - if(DataServiceScopeStack.Count>0) - DataServiceScopeStack.Pop(); + Verify.That(DataServiceScopeStack.Count > 0, nameof(DataServiceScopeStack) + " underflow"); + + DataServiceScopeStack.Pop(); } + internal static void PushDataServiceScope() { DataServiceScopeStack.Push(new List()); From 413023ef6c53d2a4f09849ac8173309914f9af79 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 20 May 2016 13:57:10 +0200 Subject: [PATCH 033/213] Fixing occasional YSOD caused by DataScope objects being disposed multiple times --- .../VersionHttpModule.cs | 25 ++++++----- .../Core/Parallelization/ParallelFacade.cs | 3 ++ Composite/Core/Threading/ThreadDataManager.cs | 14 ++---- ...AdministrativeDataScopeSetterHttpModule.cs | 43 +++++++++---------- Composite/Data/DataScope.cs | 30 ++++++++++--- Composite/Data/DataServiceScopeManager.cs | 33 +++++++++++--- 6 files changed, 91 insertions(+), 57 deletions(-) diff --git a/Composite.VersionPublishing/VersionHttpModule.cs b/Composite.VersionPublishing/VersionHttpModule.cs index 5e21298866..6c7829339d 100644 --- a/Composite.VersionPublishing/VersionHttpModule.cs +++ b/Composite.VersionPublishing/VersionHttpModule.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Web; using Composite.Core.Extensions; using Composite.Core.Routing.Pages; @@ -8,20 +9,21 @@ namespace Composite.VersionPublishing { class VersionHttpModule : IHttpModule { - private DataConnection _dataConnection; + private const string HttpContextItemsKey = nameof(VersionHttpModule) + ":DataScope"; - private void SetVersioningServiceFromUrlPath(string pathInfo) + private void SetVersioningServiceFromUrlPath(DataScope dataScope, string pathInfo) { if (VersionNameUrlHelper.CheckIfAdminUrl(pathInfo)) { - _dataConnection.AddService(VersioningServiceSettings.NoFiltering()); + dataScope.AddService(VersioningServiceSettings.NoFiltering()); return; } + var versionName = VersionNameUrlHelper.ResolveVersionName(pathInfo); if (!versionName.IsNullOrEmpty()) { C1PageRoute.RegisterPathInfoUsage(); - _dataConnection.AddService(VersioningServiceSettings.ByName(versionName)); + dataScope.AddService(VersioningServiceSettings.ByName(versionName)); } } @@ -33,17 +35,20 @@ public void Init(HttpApplication context) private void context_EndRequest(object sender, EventArgs e) { - _dataConnection.Dispose(); + var httpContext = ((HttpApplication)sender).Context; + + var dataScope = (DataScope) httpContext.Items[HttpContextItemsKey]; + dataScope.Dispose(); } private void context_BeginRequest(object sender, EventArgs e) { - var httpApplication = sender as HttpApplication; - if (httpApplication == null) return; + var httpContext = ((HttpApplication)sender).Context; + + var dataScope = new DataScope(null as CultureInfo); + httpContext.Items[HttpContextItemsKey] = dataScope; - _dataConnection = new DataConnection(); - var httpContext = httpApplication.Context; - SetVersioningServiceFromUrlPath(httpContext.Request.Path); + SetVersioningServiceFromUrlPath(dataScope, httpContext.Request.Path); } public void Dispose() diff --git a/Composite/Core/Parallelization/ParallelFacade.cs b/Composite/Core/Parallelization/ParallelFacade.cs index ec21e937f2..e4b9e8159a 100644 --- a/Composite/Core/Parallelization/ParallelFacade.cs +++ b/Composite/Core/Parallelization/ParallelFacade.cs @@ -216,6 +216,8 @@ public void WrapperAction(TSource source) languageScopePushed = true; } + DataServiceScopeManager.EnterThreadLocal(); + HttpContext.Current = _parentThreadHttpContext; currentThread.CurrentCulture = _parentThreadCulture; @@ -259,6 +261,7 @@ public void WrapperAction(TSource source) DataScopeManager.ExitThreadLocal(); LocalizationScopeManager.ExitThreadLocal(); + DataServiceScopeManager.ExitThreadLocal(); } } } diff --git a/Composite/Core/Threading/ThreadDataManager.cs b/Composite/Core/Threading/ThreadDataManager.cs index c086d4c0ba..526ea1ce70 100644 --- a/Composite/Core/Threading/ThreadDataManager.cs +++ b/Composite/Core/Threading/ThreadDataManager.cs @@ -14,7 +14,7 @@ public static class ThreadDataManager private const string c_HttpContextItemsId = "ThreadDataManager"; [ThreadStatic] - private static ThreadDataManagerData _threadDataManagerData = null; + private static ThreadDataManagerData _threadDataManagerData; /// /// Gets object for the current thread @@ -88,10 +88,7 @@ public static ThreadDataManagerData GetCurrentNotNull() public static ThreadDataManagerData CreateNew() { var current = Current; - if(current != null) - { - current.CheckNotDisposed(); - } + current?.CheckNotDisposed(); return new ThreadDataManagerData(current); } @@ -111,10 +108,7 @@ public static IDisposable Initialize() [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static IDisposable Initialize(ThreadDataManagerData parentThreadData) { - if(parentThreadData != null) - { - parentThreadData.CheckNotDisposed(); - } + parentThreadData?.CheckNotDisposed(); return new ThreadDataManagerScope(new ThreadDataManagerData(parentThreadData), true); } @@ -261,7 +255,7 @@ public void Dispose() } catch(Exception e) { - Core.Logging.LoggingService.LogError(LogTitle, e); + Log.LogError(LogTitle, e); } Verify.IsTrue(Current == _threadData, "ThreadDataManager.Current points to a different thread data object!!!"); diff --git a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs index afbaaf36ee..c2cb5bae39 100644 --- a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs +++ b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs @@ -10,39 +10,36 @@ namespace Composite.Core.WebClient.HttpModules { internal class AdministrativeDataScopeSetterHttpModule : IHttpModule { + private const string HttpContextItemsKey = nameof(AdministrativeDataScopeSetterHttpModule) + ":DataScope"; + public void Init(HttpApplication context) { - var moduleInstance = new ModuleInstance(); - context.AuthenticateRequest += moduleInstance.AuthenticateRequest; - context.EndRequest += moduleInstance.EndRequest; + context.AuthenticateRequest += AuthenticateRequest; + context.EndRequest += EndRequest; } - private class ModuleInstance + private static void AuthenticateRequest(object sender, EventArgs e) { - private DataScope _dataScope; - - public void AuthenticateRequest(object sender, EventArgs e) - { - if (SystemSetupFacade.IsSystemFirstTimeInitialized == false) return; + if (!SystemSetupFacade.IsSystemFirstTimeInitialized) return; - HttpApplication application = (HttpApplication)sender; - HttpContext context = application.Context; + HttpContext context = ((HttpApplication)sender).Context; - bool adminRootRequest = UrlUtils.IsAdminConsoleRequest(context); - - if (adminRootRequest && UserValidationFacade.IsLoggedIn()) - { - _dataScope = new DataScope(DataScopeIdentifier.Administrated, UserSettings.ActiveLocaleCultureInfo); - } - } + bool adminRootRequest = UrlUtils.IsAdminConsoleRequest(context); - public void EndRequest(object sender, EventArgs e) + if (adminRootRequest && UserValidationFacade.IsLoggedIn()) { - if (_dataScope != null) - { - _dataScope.Dispose(); - } + var dataScope = new DataScope(DataScopeIdentifier.Administrated, UserSettings.ActiveLocaleCultureInfo); + context.Items[HttpContextItemsKey] = dataScope; } + + } + + private static void EndRequest(object sender, EventArgs e) + { + HttpContext context = ((HttpApplication)sender).Context; + var dataScope = context.Items[HttpContextItemsKey] as DataScope; + + dataScope?.Dispose(); } public void Dispose() diff --git a/Composite/Data/DataScope.cs b/Composite/Data/DataScope.cs index d649260dfd..0609983dec 100644 --- a/Composite/Data/DataScope.cs +++ b/Composite/Data/DataScope.cs @@ -10,9 +10,10 @@ namespace Composite.Data [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public sealed class DataScope : IDisposable { - private readonly bool _dataScopePushed = false; - private readonly bool _cultureInfoPushed = false; - private bool _dataServicePushed = false; + private readonly bool _dataScopePushed; + private readonly bool _cultureInfoPushed; + private bool _dataServicePushed; + private bool _disposed; /// public void AddService(object service) @@ -93,14 +94,28 @@ public DataScope(PublicationScope publicationScope, CultureInfo cultureInfo) /// ~DataScope() { - Dispose(); + Dispose(false); } - - /// public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + + if (_disposed) + { + throw new ObjectDisposedException(nameof(DataScope)); + } + if (_dataScopePushed) { DataScopeManager.PopDataScope(); @@ -114,8 +129,9 @@ public void Dispose() if (_dataServicePushed) { DataServiceScopeManager.PopDataServiceScope(); - _dataServicePushed = false; } + + _disposed = true; } } } diff --git a/Composite/Data/DataServiceScopeManager.cs b/Composite/Data/DataServiceScopeManager.cs index 47ea65e4d1..0a28a64fdc 100644 --- a/Composite/Data/DataServiceScopeManager.cs +++ b/Composite/Data/DataServiceScopeManager.cs @@ -45,13 +45,32 @@ private static Stack> DataServiceScopeStack { get { - var threadLocalStack = CallContext.GetData(ThreadLocalCacheKey) as Stack>; - if (threadLocalStack != null) - { - return threadLocalStack; - } + return CallContext.GetData(ThreadLocalCacheKey) as Stack> + ?? RequestLifetimeCache.GetCachedOrNew>>("DataServiceScopeManager:Stack"); + } + } - return RequestLifetimeCache.GetCachedOrNew>>("DataServiceScopeManager:Stack"); + /// + /// Move the stack handling scope to a thread local store, enabling simultaneous threads to mutate (their own) scope. This will be in effect untill the thread has completed. + /// + public static void EnterThreadLocal() + { + if (CallContext.GetData(ThreadLocalCacheKey) == null) + { + var threadLocalStack = new Stack>(DataServiceScopeStack); + CallContext.SetData(ThreadLocalCacheKey, threadLocalStack); + } + } + + + /// + /// Move the stack handling to request scope. + /// + public static void ExitThreadLocal() + { + if (CallContext.GetData(ThreadLocalCacheKey) != null) + { + CallContext.SetData(ThreadLocalCacheKey, null); } } @@ -63,7 +82,7 @@ private static Stack> DataServiceScopeStack internal static void PopDataServiceScope() { Verify.That(DataServiceScopeStack.Count > 0, nameof(DataServiceScopeStack) + " underflow"); - + DataServiceScopeStack.Pop(); } From dc5b3214ddccb1d5b1f7035669139939d28b8d13 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 20 May 2016 13:58:25 +0200 Subject: [PATCH 034/213] Fixing thread unsafe code in DataConnection --- Composite/Data/DataConnection.cs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Composite/Data/DataConnection.cs b/Composite/Data/DataConnection.cs index 52a2af0a16..f987f0316c 100644 --- a/Composite/Data/DataConnection.cs +++ b/Composite/Data/DataConnection.cs @@ -25,9 +25,10 @@ namespace Composite.Data public class DataConnection : ImplementationContainer, IDisposable { //private ImplementationContainer _pageDataConnection; - private ImplementationContainer _sitemapNavigator; + private readonly ImplementationContainer _sitemapNavigator; + + private bool _disposed; - private static DataConnectionImplementation _connectionImplementation; /// /// Resolve service of a specific type that is attached to connection's data scope @@ -45,7 +46,7 @@ public object GetService(Type t) /// public void AddService(object service) { - _connectionImplementation.DataScope.AddService(service); + Implementation.DataScope.AddService(service); } /// @@ -67,7 +68,7 @@ public void AddService(object service) /// /// public DataConnection() - : base(() => _connectionImplementation = ImplementationFactory.CurrentFactory.CreateDataConnection(null, null)) + : base(() => ImplementationFactory.CurrentFactory.CreateDataConnection(null, null)) { CreateImplementation(); @@ -95,7 +96,7 @@ public DataConnection() /// /// public DataConnection(PublicationScope scope) - : base(() => _connectionImplementation = ImplementationFactory.CurrentFactory.CreateDataConnection(scope, null)) + : base(() => ImplementationFactory.CurrentFactory.CreateDataConnection(scope, null)) { if ((scope < PublicationScope.Unpublished) || (scope > PublicationScope.Published)) throw new ArgumentOutOfRangeException("scope"); @@ -125,7 +126,7 @@ public DataConnection(PublicationScope scope) /// /// public DataConnection(CultureInfo locale) - : base(() => _connectionImplementation = ImplementationFactory.CurrentFactory.CreateDataConnection(null, locale)) + : base(() => ImplementationFactory.CurrentFactory.CreateDataConnection(null, locale)) { CreateImplementation(); @@ -154,7 +155,7 @@ public DataConnection(CultureInfo locale) /// /// public DataConnection(PublicationScope scope, CultureInfo locale) - : base(() => _connectionImplementation = ImplementationFactory.CurrentFactory.CreateDataConnection(scope, locale)) + : base(() => ImplementationFactory.CurrentFactory.CreateDataConnection(scope, locale)) { if ((scope < PublicationScope.Unpublished) || (scope > PublicationScope.Published)) throw new ArgumentOutOfRangeException("scope"); @@ -522,12 +523,18 @@ public void Dispose() /// protected virtual void Dispose(bool disposing) { - if (disposing) + if (!disposing) return; + + if (_disposed) { - this.DisposeImplementation(); - //_pageDataConnection.Implementation.DisposeImplementation(); - _sitemapNavigator.Implementation.DisposeImplementation(); + throw new ObjectDisposedException(nameof(DataConnection)); } + + this.DisposeImplementation(); + + _sitemapNavigator.Implementation.DisposeImplementation(); + + _disposed = true; } } } From e6cee68ccf3cc1bf98846b24259effef4b34786a Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 20 May 2016 15:42:17 +0200 Subject: [PATCH 035/213] Fixing VersionHttpModule --- Composite.VersionPublishing/VersionHttpModule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Composite.VersionPublishing/VersionHttpModule.cs b/Composite.VersionPublishing/VersionHttpModule.cs index 6c7829339d..56a41af76e 100644 --- a/Composite.VersionPublishing/VersionHttpModule.cs +++ b/Composite.VersionPublishing/VersionHttpModule.cs @@ -37,8 +37,8 @@ private void context_EndRequest(object sender, EventArgs e) { var httpContext = ((HttpApplication)sender).Context; - var dataScope = (DataScope) httpContext.Items[HttpContextItemsKey]; - dataScope.Dispose(); + var dataScope = httpContext.Items[HttpContextItemsKey] as DataScope; + dataScope?.Dispose(); } private void context_BeginRequest(object sender, EventArgs e) From 408f01a14293d811acf57cf95c0b436ae27878cc Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 20 May 2016 15:45:35 +0200 Subject: [PATCH 036/213] Fixing not properly disposed DataConnection objects --- Composite/AspNet/Razor/CompositeC1WebPage.cs | 47 +++++++-------- .../DataConnectionImplementation.cs | 57 +++++++++---------- .../RazorBasedFunction.cs | 24 +++++--- 3 files changed, 62 insertions(+), 66 deletions(-) diff --git a/Composite/AspNet/Razor/CompositeC1WebPage.cs b/Composite/AspNet/Razor/CompositeC1WebPage.cs index e9d278be86..0e468a38d8 100644 --- a/Composite/AspNet/Razor/CompositeC1WebPage.cs +++ b/Composite/AspNet/Razor/CompositeC1WebPage.cs @@ -15,7 +15,7 @@ namespace Composite.AspNet.Razor public abstract class CompositeC1WebPage : WebPage, IDisposable { private bool _disposed; - private readonly DataConnection _data; + private DataConnection _data; /// /// Initializes a new instance of the class. @@ -28,36 +28,24 @@ protected CompositeC1WebPage() /// /// Gets a object. /// - public DataConnection Data - { - get { return _data; } - } + public DataConnection Data => _data; /// /// Gets a object. /// - public SitemapNavigator Sitemap - { - get { return Data.SitemapNavigator; } - } + public SitemapNavigator Sitemap => Data.SitemapNavigator; /// /// Gets the home page node. /// - public PageNode HomePageNode - { - get { return Sitemap.CurrentHomePageNode; } - } + public PageNode HomePageNode => Sitemap.CurrentHomePageNode; /// /// Gets the current page node. /// - public PageNode CurrentPageNode - { - get { return Sitemap.CurrentPageNode; } - } + public PageNode CurrentPageNode => Sitemap.CurrentPageNode; /// @@ -147,6 +135,14 @@ private FunctionContextContainer GetFunctionContext() return PageData[RazorHelper.PageContext_FunctionContextContainer]; } + public override void ExecutePageHierarchy() + { + base.ExecutePageHierarchy(); + + _data.Dispose(); + _data = null; + } + /// public void Dispose() { @@ -158,15 +154,14 @@ public void Dispose() /// protected virtual void Dispose(bool disposing) { - if (!_disposed) - { - if (disposing) - { - _data.Dispose(); - } - - _disposed = true; - } + if (_disposed) return; + + if (disposing) + { + _data?.Dispose(); + } + + _disposed = true; } /// diff --git a/Composite/Core/Implementation/DataConnectionImplementation.cs b/Composite/Core/Implementation/DataConnectionImplementation.cs index 52eff5c57f..3a98273486 100644 --- a/Composite/Core/Implementation/DataConnectionImplementation.cs +++ b/Composite/Core/Implementation/DataConnectionImplementation.cs @@ -1,5 +1,8 @@ -using System; +//#define ConnectionLeakCheck + +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using Composite.Core.Threading; @@ -13,6 +16,10 @@ namespace Composite.Core.Implementation /// public class DataConnectionImplementation : DataConnectionBase, IDisposable { +#if ConnectionLeakCheck + private string _allocationCallStack; +#endif + private IDisposable _threadDataManager; private readonly DataScope _dataScope; @@ -44,14 +51,18 @@ public DataConnectionImplementation(PublicationScope scope, CultureInfo locale) private void InitializeThreadData() { _threadDataManager = ThreadDataManager.EnsureInitialize(); - } - /// - /// Documentation pending - /// - /// - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get", Justification = "This is what we want")] +#if ConnectionLeakCheck + _allocationCallStack = new StackTrace().ToString(); +#endif + } + + /// + /// Documentation pending + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get", Justification = "This is what we want")] public virtual IQueryable Get() where TData : class, IData { @@ -178,39 +189,19 @@ public virtual TData New() /// /// Documentation pending /// - public virtual PublicationScope CurrentPublicationScope - { - get - { - return this.PublicationScope; - } - } - + public virtual PublicationScope CurrentPublicationScope => this.PublicationScope; /// /// Documentation pending /// - public virtual CultureInfo CurrentLocale - { - get - { - return this.Locale; - } - } + public virtual CultureInfo CurrentLocale => this.Locale; /// /// Documentation pending /// - public virtual IEnumerable AllLocales - { - get - { - return DataLocalizationFacade.ActiveLocalizationCultures; - } - } - + public virtual IEnumerable AllLocales => DataLocalizationFacade.ActiveLocalizationCultures; /// @@ -227,6 +218,10 @@ public void Dispose() /// ~DataConnectionImplementation() { +#if ConnectionLeakCheck + Log.LogError(nameof(DataConnection), "Not disposed data connection allocated at: " + _allocationCallStack); +#endif + Dispose(false); } diff --git a/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorBasedFunction.cs b/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorBasedFunction.cs index be5d072bc9..8501dacbdb 100644 --- a/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorBasedFunction.cs +++ b/Composite/Plugins/Functions/FunctionProviders/RazorFunctionProvider/RazorBasedFunction.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Web.WebPages; using Composite.AspNet.Razor; -using Composite.Core.Extensions; using Composite.Core.IO; using Composite.Core.WebClient; using Composite.Core.Xml; @@ -28,19 +27,26 @@ public RazorBasedFunction(string ns, string name, string description, Type retur protected override void InitializeParameters() { - WebPageBase razorPage; + WebPageBase razorPage = null; - using (BuildManagerHelper.DisableUrlMetadataCachingScope()) + try { - razorPage = WebPage.CreateInstanceFromVirtualPath(VirtualPath); - } + using (BuildManagerHelper.DisableUrlMetadataCachingScope()) + { + razorPage = WebPage.CreateInstanceFromVirtualPath(VirtualPath); + } - if (!(razorPage is RazorFunction)) + if (!(razorPage is RazorFunction)) + { + throw new InvalidOperationException($"Failed to initialize function from cache. Path: '{VirtualPath}'"); + } + + Parameters = FunctionBasedFunctionProviderHelper.GetParameters(razorPage as RazorFunction, typeof (RazorFunction), VirtualPath); + } + finally { - throw new InvalidOperationException("Failed to initialize function from cache. Path: '{0}'".FormatWith(VirtualPath)); + (razorPage as IDisposable)?.Dispose(); } - - Parameters = FunctionBasedFunctionProviderHelper.GetParameters(razorPage as RazorFunction, typeof(RazorFunction), VirtualPath); } public override object Execute(ParameterList parameters, FunctionContextContainer context) From f1be0e681603bedd912cdcb0b43fcc18fc452152 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 23 May 2016 14:52:51 +0200 Subject: [PATCH 037/213] Refactoring --- Composite/C1Console/Forms/FormTreeCompiler.cs | 30 +++---- .../FormTreeCompiler/PropertyAssigner.cs | 18 +++-- .../BuildinProducers/BindingsProducer.cs | 15 +--- .../Workflow/Activities/FormsWorkflow.cs | 81 +++++++++---------- .../C1Console/Workflow/Foundation/FormData.cs | 15 ++-- .../Workflow/WorkflowFlowController.cs | 7 +- 6 files changed, 75 insertions(+), 91 deletions(-) diff --git a/Composite/C1Console/Forms/FormTreeCompiler.cs b/Composite/C1Console/Forms/FormTreeCompiler.cs index f4371f3778..ea0d5cabab 100644 --- a/Composite/C1Console/Forms/FormTreeCompiler.cs +++ b/Composite/C1Console/Forms/FormTreeCompiler.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Xml; using System.Xml.Linq; using Composite.C1Console.Forms.Foundation.FormTreeCompiler; @@ -81,11 +82,13 @@ public void Compile(XDocument doc, IFormChannelIdentifier channel, Dictionary GetBindingToClientIDMapping() } /// - public IUiControl UiControl - { - get { return _uiControl; } - } + public IUiControl UiControl => _uiControl; /// @@ -175,22 +175,16 @@ public ResourceHandle Icon string[] resourceParts = _iconHandle.Split(','); if (resourceParts.Length != 2) throw new InvalidOperationException( - string.Format("Invalid icon resource name '{0}'. Only one comma expected.", _iconHandle)); + $"Invalid icon resource name '{_iconHandle}'. Only one comma expected."); return new ResourceHandle(resourceParts[0].Trim(), resourceParts[1].Trim()); } } /// - public Dictionary BindingObjects - { - get { return _bindingObjects; } - } + public Dictionary BindingObjects => _bindingObjects; /// - public CompileTreeNode RootCompileTreeNode - { - get { return _rootCompilerNode; } - } + public CompileTreeNode RootCompileTreeNode => _rootCompilerNode; } } diff --git a/Composite/C1Console/Forms/Foundation/FormTreeCompiler/PropertyAssigner.cs b/Composite/C1Console/Forms/Foundation/FormTreeCompiler/PropertyAssigner.cs index 707bf02542..1315343007 100644 --- a/Composite/C1Console/Forms/Foundation/FormTreeCompiler/PropertyAssigner.cs +++ b/Composite/C1Console/Forms/Foundation/FormTreeCompiler/PropertyAssigner.cs @@ -375,24 +375,30 @@ private static void EvaluteObjectBinding(ElementCompileTreeNode element, Propert private static void ResolveBindingObject(ElementCompileTreeNode element, PropertyCompileTreeNode property, CompileContext compileContext, string bindSourceName, out object bindingObject, out Type bindType) { - string typeName = ((BindingsProducer)compileContext.BindingsProducer).GetTypeNameByName(bindSourceName); + var bindingProducer = (BindingsProducer) compileContext.BindingsProducer; + if (bindingProducer == null) + { + throw new FormCompileException($"Failed to resolve binding object {bindSourceName} - the binding producer is null", element, property); + } + + string typeName = bindingProducer.GetTypeNameByName(bindSourceName); if (typeName == null) { - throw new FormCompileException(string.Format("{1} binds to an undeclared binding name '{0}'. All binding names must be declared in /cms:formdefinition/cms:bindings", bindSourceName, element.XmlSourceNodeInformation.XPath), element, property); + throw new FormCompileException($"{element.XmlSourceNodeInformation.XPath} binds to an undeclared binding name '{bindSourceName}'. All binding names must be declared in /cms:formdefinition/cms:bindings", element, property); } bindType = TypeManager.TryGetType(typeName); if (bindType == null) { - throw new FormCompileException(string.Format("The form binding '{0}' is declared as an unknown type '{1}'", bindSourceName, typeName), element, property); + throw new FormCompileException($"The form binding '{bindSourceName}' is declared as an unknown type '{typeName}'", element, property); } - bool? optional = ((BindingsProducer)compileContext.BindingsProducer).GetOptionalValueByName(bindSourceName); + bool? optional = bindingProducer.GetOptionalValueByName(bindSourceName); bindingObject = compileContext.GetBindingObject(bindSourceName); if (!optional.Value && !compileContext.BindingObjectExists(bindSourceName)) { - throw new FormCompileException(string.Format("The binding object named '{0}' not found in the input dictionary", bindSourceName), element, property); + throw new FormCompileException($"The binding object named '{bindSourceName}' not found in the input dictionary", element, property); } if (bindingObject != null) @@ -400,7 +406,7 @@ private static void ResolveBindingObject(ElementCompileTreeNode element, Propert Type bindingObjectType = bindingObject.GetType(); if (!bindType.IsAssignableOrLazyFrom(bindingObjectType)) { - throw new FormCompileException(string.Format("The binding object named '{0}' from the input dictionary is not of expected type '{1}', but '{2}'", bindSourceName, bindType.FullName, bindingObjectType.FullName), element, property); + throw new FormCompileException($"The binding object named '{bindSourceName}' from the input dictionary is not of expected type '{bindType.FullName}', but '{bindingObjectType.FullName}'", element, property); } } } diff --git a/Composite/C1Console/Forms/StandardProducerMediators/BuildinProducers/BindingsProducer.cs b/Composite/C1Console/Forms/StandardProducerMediators/BuildinProducers/BindingsProducer.cs index 2dbc1e0850..dea4c2113f 100644 --- a/Composite/C1Console/Forms/StandardProducerMediators/BuildinProducers/BindingsProducer.cs +++ b/Composite/C1Console/Forms/StandardProducerMediators/BuildinProducers/BindingsProducer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Composite.C1Console.Forms.StandardProducerMediators.BuildinProducers @@ -19,22 +20,12 @@ public List bindings public string GetTypeNameByName(string name) { - foreach (BindingProducer bp in _bindings) - { - if (bp.name == name) return bp.type; - } - - return null; + return _bindings.FirstOrDefault(bp => bp.name == name)?.type; } public bool? GetOptionalValueByName(string name) { - foreach (BindingProducer bp in _bindings) - { - if (bp.name == name) return bp.optional; - } - - return null; + return _bindings.FirstOrDefault(bp => bp.name == name)?.optional; } } } diff --git a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs index 7487006359..b16bc892c3 100644 --- a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs +++ b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs @@ -29,7 +29,7 @@ namespace Composite.C1Console.Workflow.Activities { - /// + /// /// /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] @@ -243,7 +243,7 @@ public T GetBinding(string name) object obj; if (!Bindings.TryGetValue(name, out obj)) { - throw new InvalidOperationException(string.Format("The binding named '{0}' was not found", name)); + throw new InvalidOperationException($"The binding named '{name}' was not found"); } return (T)obj; @@ -315,7 +315,7 @@ public string Payload _workflowActionToken = this.ActionToken as WorkflowActionToken; } - return _workflowActionToken == null ? null : _workflowActionToken.Payload; + return _workflowActionToken?.Payload; } } @@ -331,32 +331,30 @@ public string ExtraPayload _workflowActionToken = this.ActionToken as WorkflowActionToken; } - return _workflowActionToken == null ? null : _workflowActionToken.ExtraPayload; + return _workflowActionToken?.ExtraPayload; } } - internal Guid InstanceId - { - get - { - return _instanceId; - } - } + internal Guid InstanceId => _instanceId; + private static FlowControllerServicesContainer GetFlowControllerServicesContainer() + { + return WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + } /// protected void ReportException(Exception ex) { - if (ex == null) throw new ArgumentNullException("ex"); + Verify.ArgumentNotNull(ex, nameof(ex)); - this.ShowMessage(DialogType.Error, "An unfortunate error occurred", string.Format("Sorry, but an error has occurred, preventing the opperation from completing as expected. The error has been documented in details so a technican may follow up on this issue.\n\nThe error message is: {0}", ex.Message)); + this.ShowMessage(DialogType.Error, "An unfortunate error occurred", $"Sorry, but an error has occurred, preventing the opperation from completing as expected. The error has been documented in details so a technican may follow up on this issue.\n\nThe error message is: {ex.Message}"); Log.LogCritical(this.GetType().Name, ex); - FlowControllerServicesContainer container = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var container = GetFlowControllerServicesContainer(); IManagementConsoleMessageService service = container.GetService(); service.ShowLogEntry(this.GetType(), ex); } @@ -366,7 +364,7 @@ protected void ReportException(Exception ex) /// protected void LogMessage(LogLevel logLevel, string message) { - FlowControllerServicesContainer container = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var container = GetFlowControllerServicesContainer(); IManagementConsoleMessageService service = container.GetService(); service.ShowLogEntry(this.GetType(), logLevel, message); @@ -396,7 +394,7 @@ protected void LogMessage(LogLevel logLevel, string message) /// protected void ShowMessage(DialogType dialogType, string title, string message) { - FlowControllerServicesContainer container = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var container = GetFlowControllerServicesContainer(); IManagementConsoleMessageService service = container.GetService(); @@ -415,7 +413,7 @@ protected void ShowMessage(DialogType dialogType, string title, string message) /// protected void SelectElement(EntityToken entityToken) { - FlowControllerServicesContainer container = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var container = GetFlowControllerServicesContainer(); IManagementConsoleMessageService service = container.GetService(); @@ -427,7 +425,7 @@ protected void SelectElement(EntityToken entityToken) /// protected void RebootConsole() { - FlowControllerServicesContainer container = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var container = GetFlowControllerServicesContainer(); IManagementConsoleMessageService service = container.GetService(); @@ -439,9 +437,9 @@ protected void RebootConsole() /// protected void ShowFieldMessage(string fieldBindingPath, string message) { - FlowControllerServicesContainer flowControllerServicesContainer = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var flowControllerServicesContainer = GetFlowControllerServicesContainer(); - IFormFlowRenderingService formFlowRenderingService = flowControllerServicesContainer.GetService(); + var formFlowRenderingService = flowControllerServicesContainer.GetService(); formFlowRenderingService.ShowFieldMessage(fieldBindingPath, StringResourceSystemFacade.ParseString(message)); } @@ -476,25 +474,22 @@ protected void SetSaveStatus(bool succeeded, EntityToken entityToken) /// protected void SetSaveStatus(bool succeeded, string serializedEntityToken) { - SaveWorklowTaskManagerEvent saveWorklowTaskManagerEvent = new SaveWorklowTaskManagerEvent - ( - new WorkflowFlowToken(this.InstanceId), - this.WorkflowInstanceId, - succeeded - ); + var saveWorklowTaskManagerEvent = new SaveWorklowTaskManagerEvent + ( + new WorkflowFlowToken(this.InstanceId), + this.WorkflowInstanceId, + succeeded + ); - FlowControllerServicesContainer container = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); - ITaskManagerFlowControllerService service = container.GetService(); + var container = GetFlowControllerServicesContainer(); + var service = container.GetService(); service.OnStatus(saveWorklowTaskManagerEvent); var flowRenderingService = container.GetService(); - if(flowRenderingService != null) - { - flowRenderingService.SetSaveStatus(succeeded); - } + flowRenderingService?.SetSaveStatus(succeeded); - - IManagementConsoleMessageService managementConsoleMessageService = container.GetService(); + + var managementConsoleMessageService = container.GetService(); managementConsoleMessageService.SaveStatus(succeeded); // TO BE REMOVED if (serializedEntityToken != null) @@ -509,7 +504,7 @@ protected void SetSaveStatus(bool succeeded, string serializedEntityToken) /// protected void CloseCurrentView() { - FlowControllerServicesContainer flowControllerServicesContainer = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var flowControllerServicesContainer = GetFlowControllerServicesContainer(); var managementConsoleMessageService = flowControllerServicesContainer.GetService(); @@ -524,7 +519,7 @@ protected void CloseCurrentView() /// protected void LockTheSystem() { - FlowControllerServicesContainer flowControllerServicesContainer = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var flowControllerServicesContainer = GetFlowControllerServicesContainer(); IManagementConsoleMessageService managementConsoleMessageService = flowControllerServicesContainer.GetService(); @@ -536,7 +531,7 @@ protected void LockTheSystem() /// protected void RerenderView() { - FlowControllerServicesContainer flowControllerServicesContainer = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var flowControllerServicesContainer = GetFlowControllerServicesContainer(); IFormFlowRenderingService formFlowRenderingService = flowControllerServicesContainer.GetService(); formFlowRenderingService.RerenderView(); } @@ -546,8 +541,8 @@ protected void RerenderView() /// protected void CollapseAndRefresh() { - FlowControllerServicesContainer container = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); - IManagementConsoleMessageService service = container.GetService(); + var container = GetFlowControllerServicesContainer(); + var service = container.GetService(); service.CollapseAndRefresh(); } @@ -556,7 +551,7 @@ protected void CollapseAndRefresh() /// protected string GetCurrentConsoleId() { - FlowControllerServicesContainer flowControllerServicesContainer = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var flowControllerServicesContainer = GetFlowControllerServicesContainer(); IManagementConsoleMessageService managementConsoleMessageService = flowControllerServicesContainer.GetService(); @@ -576,7 +571,7 @@ protected IEnumerable GetConsoleIdsOpenedByCurrentUser() /// protected void ExecuteAction(EntityToken entityToken, ActionToken actionToken) { - FlowControllerServicesContainer flowControllerServicesContainer = WorkflowFacade.GetFlowControllerServicesContainer(WorkflowEnvironment.WorkflowInstanceId); + var flowControllerServicesContainer = GetFlowControllerServicesContainer(); IActionExecutionService actionExecutionService = flowControllerServicesContainer.GetService(); @@ -637,7 +632,7 @@ protected void SetCustomToolbarDefinition(string customToolbarDefinition) protected void SetCustomToolbarDefinition(IFormMarkupProvider customToolbarMarkupProvider) { ExternalDataExchangeService externalDataExchangeService = WorkflowFacade.WorkflowRuntime.GetService(); - IFormsWorkflowActivityService formsWorkflowActivityService = externalDataExchangeService.GetService(typeof(IFormsWorkflowActivityService)) as IFormsWorkflowActivityService; + var formsWorkflowActivityService = externalDataExchangeService.GetService(typeof(IFormsWorkflowActivityService)) as IFormsWorkflowActivityService; formsWorkflowActivityService.DeliverCustomToolbarDefinition(WorkflowEnvironment.WorkflowInstanceId, customToolbarMarkupProvider); } @@ -839,7 +834,7 @@ protected bool BindAndValidate(DataTypeDescriptorFormsHelper helper, IData data) && (fieldValue as string) == string.Empty && !helper.BindingIsOptional(bindingName)) { - this.ShowFieldMessage(bindingName, StringResourceSystemFacade.GetString("Composite.Management", "Validation.RequiredField")); + this.ShowFieldMessage(bindingName, LocalizationFiles.Composite_Management.Validation_RequiredField); isValid = false; } diff --git a/Composite/C1Console/Workflow/Foundation/FormData.cs b/Composite/C1Console/Workflow/Foundation/FormData.cs index 7b284352c1..5ed1b7f6ad 100644 --- a/Composite/C1Console/Workflow/Foundation/FormData.cs +++ b/Composite/C1Console/Workflow/Foundation/FormData.cs @@ -130,13 +130,14 @@ public XElement Serialize() XElement excludedEventsElement = xmlSerializer.Serialize(typeof(List), ExcludedEvents); - XElement formDataElement = new XElement("FormData"); - formDataElement.Add(containerLabelElement); - formDataElement.Add(formDefinitionElement); - formDataElement.Add(customToolbarDefinitionElement); - formDataElement.Add(new XElement("ContainerType", containerTypeElement)); - formDataElement.Add(new XElement("Bindings", bindingsElement)); - formDataElement.Add(new XElement("BindingsValidationRules", bindingsValidationRulesElement)); + var formDataElement = new XElement("FormData", + containerLabelElement, + formDefinitionElement, + customToolbarDefinitionElement, + new XElement("ContainerType", containerTypeElement), + new XElement("Bindings", bindingsElement), + new XElement("BindingsValidationRules", bindingsValidationRulesElement) + ); if (excludedEventsElement != null) formDataElement.Add(new XElement("ExcludedEvents", excludedEventsElement)); if (this.EventHandleFilterType != null) diff --git a/Composite/C1Console/Workflow/WorkflowFlowController.cs b/Composite/C1Console/Workflow/WorkflowFlowController.cs index ac65069b9a..a99d16c9d9 100644 --- a/Composite/C1Console/Workflow/WorkflowFlowController.cs +++ b/Composite/C1Console/Workflow/WorkflowFlowController.cs @@ -117,7 +117,7 @@ private static void AddEventHandles(FormFlowUiDefinition formFlowUiDefinition, G foreach (string eventName in eventNames) { - if (formData != null && formData.ExcludedEvents != null && formData.ExcludedEvents.Contains(eventName)) continue; + if (formData?.ExcludedEvents != null && formData.ExcludedEvents.Contains(eventName)) continue; switch (eventName) { @@ -172,10 +172,7 @@ private static void AddEventHandles(FormFlowUiDefinition formFlowUiDefinition, G } IEventHandleFilter eventHandlerFilter = WorkflowFacade.GetEventHandleFilter(instanceId); - if (eventHandlerFilter != null) - { - eventHandlerFilter.Filter(formFlowUiDefinition.EventHandlers); - } + eventHandlerFilter?.Filter(formFlowUiDefinition.EventHandlers); } From bf61fa615b1c7ecd26f1bd6f903a745072e70eca Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 24 May 2016 10:52:51 +0200 Subject: [PATCH 038/213] Making EditPageWorkflow extendable --- .../PageElementProvider/EditPageWorkflow.cs | 9 ++-- .../Workflow/Activities/FormsWorkflow.cs | 43 +++++++++++++++++-- .../Workflow/FormsWorkflowExtensions.cs | 13 ++++++ .../Workflow/IFormsWorkflowExtension.cs | 36 ++++++++++++++++ Composite/Composite.csproj | 2 + 5 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 Composite/C1Console/Workflow/FormsWorkflowExtensions.cs create mode 100644 Composite/C1Console/Workflow/IFormsWorkflowExtension.cs diff --git a/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs b/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs index d45ef37bc3..e9363c4b1f 100644 --- a/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs +++ b/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/EditPageWorkflow.cs @@ -50,11 +50,12 @@ public sealed partial class EditPageWorkflow : FormsWorkflow public EditPageWorkflow() { InitializeComponent(); + InitializeExtensions(); } private static DataTypeDescriptorFormsHelper CreateDataTypeDescriptorFormsHelper(IPageMetaDataDefinition pageMetaDataDefinition, DataTypeDescriptor dataTypeDescriptor) { - var bindingPrefix = string.Format("{0}:{1}.{2}", pageMetaDataDefinition.Name, dataTypeDescriptor.Namespace, dataTypeDescriptor.Name); + var bindingPrefix = $"{pageMetaDataDefinition.Name}:{dataTypeDescriptor.Namespace}.{dataTypeDescriptor.Name}"; var helper = new DataTypeDescriptorFormsHelper(dataTypeDescriptor, bindingPrefix); @@ -149,7 +150,7 @@ private void editStateCodeActivity_ExecuteCode(object sender, EventArgs e) if (!BindingExist("SelectedPage")) { selectedPage = GetDataItemFromEntityToken(); - + if (selectedPage.PublicationStatus == GenericPublishProcessController.Published) { selectedPage.PublicationStatus = GenericPublishProcessController.Draft; @@ -280,8 +281,8 @@ private void editStateCodeActivity_ExecuteCode(object sender, EventArgs e) UpdateBinding("StateOptions", transitionNames); - var existingPagePublishSchedule = PublishScheduleHelper.GetPublishSchedule(typeof (IPage), - selectedPage.Id.ToString(), + var existingPagePublishSchedule = PublishScheduleHelper.GetPublishSchedule(typeof(IPage), + selectedPage.Id.ToString(), UserSettings.ActiveLocaleCultureInfo.Name); UpdateBinding("PublishDate", existingPagePublishSchedule?.PublishDate); diff --git a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs index b16bc892c3..e7f8964616 100644 --- a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs +++ b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs @@ -70,6 +70,19 @@ public FormsWorkflow() } + /// + protected void InitializeExtensions() + { + CanModifyActivities = true; + + foreach (var extension in FormsWorkflowExtensions.GetExtensions()) + { + extension.Initialize(this); + } + + CanModifyActivities = false; + } + /// protected override void Initialize(IServiceProvider provider) @@ -592,6 +605,8 @@ protected void ExecuteWorklow(EntityToken entityToken, Type workflowType) /// protected void DeliverFormData(string containerLabel, IFlowUiContainerType containerType, string formDefinition, Dictionary bindings, Dictionary> bindingsValidationRules) { + OnDeliverFormData(bindings, bindingsValidationRules); + ExternalDataExchangeService externalDataExchangeService = WorkflowFacade.WorkflowRuntime.GetService(); IFormsWorkflowActivityService formsWorkflowActivityService = externalDataExchangeService.GetService(typeof(IFormsWorkflowActivityService)) as IFormsWorkflowActivityService; @@ -604,21 +619,43 @@ protected void DeliverFormData(string containerLabel, IFlowUiContainerType conta /// protected void DeliverFormData(string containerLabel, IFlowUiContainerType containerType, IFormMarkupProvider formMarkupProvider, Dictionary bindings, Dictionary> bindingsValidationRules) { - ExternalDataExchangeService externalDataExchangeService = WorkflowFacade.WorkflowRuntime.GetService(); + OnDeliverFormData(bindings, bindingsValidationRules); - IFormsWorkflowActivityService formsWorkflowActivityService = externalDataExchangeService.GetService(typeof(IFormsWorkflowActivityService)) as IFormsWorkflowActivityService; + var externalDataExchangeService = WorkflowFacade.WorkflowRuntime.GetService(); + + var formsWorkflowActivityService = externalDataExchangeService.GetService(typeof(IFormsWorkflowActivityService)) as IFormsWorkflowActivityService; formsWorkflowActivityService.DeliverFormData(WorkflowEnvironment.WorkflowInstanceId, containerLabel, containerType, formMarkupProvider, bindings, bindingsValidationRules); } + + private void OnDeliverFormData(Dictionary bindings, Dictionary> bindingsValidationRules) + { + var parameters = new OnDeliverFormDataParameters() + { + Bindings = bindings, + BindingsValidationRules = bindingsValidationRules + }; + + foreach (var extension in FormsWorkflowExtensions.GetExtensions()) + { + extension.OnDeliverFormData(this, parameters); + } + } + + + + + /// /// Adds the cms:layout elements Form Definition to the UI toolbar. /// /// String containing a valid Form Definition markup document - protected void SetCustomToolbarDefinition(string customToolbarDefinition) + public void SetCustomToolbarDefinition(string customToolbarDefinition) { ExternalDataExchangeService externalDataExchangeService = WorkflowFacade.WorkflowRuntime.GetService(); + IFormsWorkflowActivityService formsWorkflowActivityService = externalDataExchangeService.GetService(typeof(IFormsWorkflowActivityService)) as IFormsWorkflowActivityService; formsWorkflowActivityService.DeliverCustomToolbarDefinition(WorkflowEnvironment.WorkflowInstanceId, customToolbarDefinition); } diff --git a/Composite/C1Console/Workflow/FormsWorkflowExtensions.cs b/Composite/C1Console/Workflow/FormsWorkflowExtensions.cs new file mode 100644 index 0000000000..6852e1f13b --- /dev/null +++ b/Composite/C1Console/Workflow/FormsWorkflowExtensions.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Composite.C1Console.Workflow +{ + public static class FormsWorkflowExtensions + { + private static readonly List _extensions = new List(); + + public static void Register(IFormsWorkflowExtension extension) => _extensions.Add(extension); + + internal static ICollection GetExtensions() => _extensions; + } +} diff --git a/Composite/C1Console/Workflow/IFormsWorkflowExtension.cs b/Composite/C1Console/Workflow/IFormsWorkflowExtension.cs new file mode 100644 index 0000000000..f73c701e58 --- /dev/null +++ b/Composite/C1Console/Workflow/IFormsWorkflowExtension.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using Composite.C1Console.Workflow.Activities; +using Composite.Data.Validation.ClientValidationRules; + +namespace Composite.C1Console.Workflow +{ + /// + public class OnDeliverFormDataParameters + { + /// + public Dictionary Bindings { get; set; } + + /// + public Dictionary> BindingsValidationRules { get; set; } + } + + /// + /// An interface for forms workflow extensions + /// + public interface IFormsWorkflowExtension + { + /// + /// In implementation custom workflow activities can be added. + /// + /// The workflow instance. + void Initialize(FormsWorkflow workflow); + + /// + /// Handles form data delivery event + /// + /// The workflow instance. + /// The parameters. + void OnDeliverFormData(FormsWorkflow workflow, OnDeliverFormDataParameters parameters); + } +} diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index faa4f5021d..cf5a1c30e7 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -165,6 +165,8 @@ + + From 1244fd1ca749e191e1d015f26ded5f5b8e5d8e8a Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 24 May 2016 13:59:35 +0200 Subject: [PATCH 039/213] Custom toolbar items - showing them before the "Save" button, now input values are bound correctly. --- .../Workflow/Activities/FormsWorkflow.cs | 2 +- .../FormFlowUiDefinitionRenderer.cs | 44 ++++++++++++------- .../AdministrativeTemplates/Document.xml | 25 ++++++----- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs index e7f8964616..5126c72285 100644 --- a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs +++ b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs @@ -595,7 +595,7 @@ protected void ExecuteAction(EntityToken entityToken, ActionToken actionToken) /// - protected void ExecuteWorklow(EntityToken entityToken, Type workflowType) + public void ExecuteWorklow(EntityToken entityToken, Type workflowType) { ExecuteAction(entityToken, new WorkflowActionToken(workflowType)); } diff --git a/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs b/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs index d1328e2dd2..16941ab8e6 100644 --- a/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs +++ b/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs @@ -96,7 +96,9 @@ public static IUiControl Render( IUiControl customToolbarItems = null; if (customToolbarItemsMarkupProvider != null) { - FormTreeCompiler toolbarCompiler = new FormTreeCompiler(); + var toolbarCompiler = new FormTreeCompiler(); + CurrentCustomToolbarFormTreeCompiler = toolbarCompiler; + using (XmlReader formMarkupReader = customToolbarItemsMarkupProvider.GetReader()) { toolbarCompiler.Compile(formMarkupReader, channel, innerFormBindings, debugMode, bindingsValidationRules); @@ -133,6 +135,16 @@ private static void BaseEventHandler(string consoleId, FormFlowEventHandler handler = eventHandlers[localScopeEventIdentifier]; Dictionary bindingErrors = activeFormTreeCompiler.SaveAndValidateControlProperties(); + FormTreeCompiler activeCustomToolbarFormTreeCompiler = CurrentCustomToolbarFormTreeCompiler; + if (activeCustomToolbarFormTreeCompiler != null) + { + var toolbarBindingErrors = activeCustomToolbarFormTreeCompiler.SaveAndValidateControlProperties(); + foreach (var pair in toolbarBindingErrors) + { + bindingErrors.Add(pair.Key, pair.Value); + } + } + formServicesContainer.AddService(new BindingValidationService(bindingErrors)); handler.Invoke(flowToken, activeInnerFormBindings, formServicesContainer); @@ -237,17 +249,10 @@ private static void BaseEventHandler(string consoleId, private static string GetFormLabelField(XDocument formMarkup) { - var labelElement = formMarkup.Descendants(Namespaces.BindingForms10 + "layout.label").FirstOrDefault(); - if (labelElement == null) - { - labelElement = formMarkup.Descendants().FirstOrDefault(e => e.Name.LocalName == "TabPanels.Label"); - } - - if (labelElement == null) return null; - - + var labelElement = formMarkup.Descendants(Namespaces.BindingForms10 + "layout.label").FirstOrDefault() + ?? formMarkup.Descendants().FirstOrDefault(e => e.Name.LocalName == "TabPanels.Label"); - var readBinding = labelElement.Element(Namespaces.BindingForms10 + "read"); + var readBinding = labelElement?.Element(Namespaces.BindingForms10 + "read"); if(readBinding == null) return null; return (string)readBinding.Attribute("source"); @@ -261,6 +266,7 @@ private static IUiContainer GetRenderingContainer(IFormChannelIdentifier channel private static readonly string _formTreeCompilerLookupKey = typeof(FormFlowUiDefinitionRenderer).FullName + "FormTreeCompiler"; + private static readonly string _customToolbarFormTreeCompilerLookupKey = typeof(FormFlowUiDefinitionRenderer).FullName + "CustomToolbarFormTreeCompiler"; private static readonly string _innerFormBindingsLookupKey = typeof(FormFlowUiDefinitionRenderer).FullName + "InnerFormBindings"; private static readonly string _currentControlTreeRoot = typeof(FormFlowUiDefinitionRenderer).FullName + "ControlTreeRoot"; private static readonly string _currentControlContainer = typeof(FormFlowUiDefinitionRenderer).FullName + "ControlContainer"; @@ -273,6 +279,12 @@ private static FormTreeCompiler CurrentFormTreeCompiler set { HttpContext.Current.Items[_formTreeCompilerLookupKey] = value; } } + private static FormTreeCompiler CurrentCustomToolbarFormTreeCompiler + { + get { return HttpContext.Current.Items[_customToolbarFormTreeCompilerLookupKey] as FormTreeCompiler; } + set { HttpContext.Current.Items[_customToolbarFormTreeCompilerLookupKey] = value; } + } + private static Dictionary CurrentInnerFormBindings { get { return HttpContext.Current.Items[_innerFormBindingsLookupKey] as Dictionary; } @@ -295,11 +307,11 @@ private static IWebUiContainer CurrentControlContainer private static void ShowFieldMessages(IWebUiControl webUiControlTreeRoot, Dictionary bindingPathedMessages, IWebUiContainer container, FlowControllerServicesContainer servicesContainer) { - Dictionary pathToClientIDMappings = new Dictionary(); + var pathToClientIDMappings = new Dictionary(); ResolveBindingPathToCliendIDMappings(webUiControlTreeRoot, pathToClientIDMappings); - Dictionary cliendIDPathedMessages = new Dictionary(); - Dictionary homelessMessages = new Dictionary(); + var cliendIDPathedMessages = new Dictionary(); + var homelessMessages = new Dictionary(); foreach (var msgElement in bindingPathedMessages) { @@ -334,9 +346,9 @@ private static void ShowFieldMessages(IWebUiControl webUiControlTreeRoot, Dictio internal static void ResolveBindingPathToCliendIDMappings(IWebUiControl webUiControl, Dictionary resolvedMappings) { - if (webUiControl is ContainerUiControlBase) + var container = webUiControl as ContainerUiControlBase; + if (container != null) { - ContainerUiControlBase container = (ContainerUiControlBase)webUiControl; foreach (IUiControl child in container.UiControls) { ResolveBindingPathToCliendIDMappings((IWebUiControl)child, resolvedMappings); diff --git a/Website/Composite/content/forms/AdministrativeTemplates/Document.xml b/Website/Composite/content/forms/AdministrativeTemplates/Document.xml index 78f4092b4a..46a72433d7 100644 --- a/Website/Composite/content/forms/AdministrativeTemplates/Document.xml +++ b/Website/Composite/content/forms/AdministrativeTemplates/Document.xml @@ -21,7 +21,19 @@ - + + + + + + + + + + + + + @@ -101,17 +113,6 @@ - - - - - - - - - - - From 641095dfe04c5a05ad407f517e5b1f0454f6ba87 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 24 May 2016 14:22:57 +0200 Subject: [PATCH 040/213] Extending edit page workflow --- .../Composite.VersionPublishing.csproj | 7 + .../EditPageWorkflowExtension.cs | 149 ++++++++++++++++++ Composite.VersionPublishing/StartupHandler.cs | 2 + 3 files changed, 158 insertions(+) create mode 100644 Composite.VersionPublishing/EditPageWorkflowExtension.cs diff --git a/Composite.VersionPublishing/Composite.VersionPublishing.csproj b/Composite.VersionPublishing/Composite.VersionPublishing.csproj index 66c07c832d..3b7a060d04 100644 --- a/Composite.VersionPublishing/Composite.VersionPublishing.csproj +++ b/Composite.VersionPublishing/Composite.VersionPublishing.csproj @@ -34,6 +34,8 @@ + + @@ -42,6 +44,7 @@ + @@ -53,6 +56,10 @@ + + {1fe08476-346a-4053-813d-8807c0e0fc36} + Composite.Workflows + {f8d87a5f-d090-4d24-80c1-dbcd938c6cab} Composite diff --git a/Composite.VersionPublishing/EditPageWorkflowExtension.cs b/Composite.VersionPublishing/EditPageWorkflowExtension.cs new file mode 100644 index 0000000000..496f797f05 --- /dev/null +++ b/Composite.VersionPublishing/EditPageWorkflowExtension.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Workflow.Activities; +using Composite.C1Console.Security; +using Composite.C1Console.Workflow; +using Composite.C1Console.Workflow.Activities; +using Composite.Core.IO; +using Composite.Data; +using Composite.Data.Types; +using Composite.Plugins.Elements.ElementProviders.PageElementProvider; + +namespace Composite.VersionPublishing +{ + internal class EditPageWorkflowExtension : IFormsWorkflowExtension + { + private class BindingNames + { + public const string SelectedPage = "SelectedPage"; + + // Custom + public const string PageVersionId = "PageVersionId"; + public const string PageVersions = "PageVersions"; + } + + const string CustomToolbarMarkupRelativePath = + "~/Composite/InstalledPackages/content/forms/Composite.VersionPublishing/EditPage.ToolBar.xml"; + + const string EditStateActivityName = "editStateActivity"; + + public void Initialize(FormsWorkflow workflow) + { + if (workflow.GetType() != typeof (EditPageWorkflow)) + { + return; + } + + // Creating an EventDrivenActivity to handle version change + var customEvent02Activity = new CustomEvent02HandleExternalEventActivity + { + EventName = "CustomEvent02", + InterfaceType = typeof (IFormsWorkflowEventService), + Name = "customEvent02Activity" + }; + + var codeActivity = new CodeActivity(); + var handler = new VersionChangeHandler(); + codeActivity.ExecuteCode += handler.OpenNewEditingWorkflow; + + var versionChange_EventDrivenActivity = new EventDrivenActivity + { + Name = "eventDrivenActivity_VersionChange", + Activities = + { + customEvent02Activity, + codeActivity, + new SetStateActivity("versionChange_setState") + { + TargetStateName = EditStateActivityName + }, + new CloseCurrentViewActivity() + } + }; + + + var editStateActivity = workflow.Activities.Single(a => a.Name == EditStateActivityName) + as StateActivity; + + // Detaching "editStateActivity" from the workflow so it can be modified + workflow.Activities.Remove(editStateActivity); + + editStateActivity.Activities.Add(versionChange_EventDrivenActivity); + + workflow.Activities.Add(editStateActivity); + } + + + public void OnDeliverFormData(FormsWorkflow workflow, OnDeliverFormDataParameters parameters) + { + if (workflow.GetType() != typeof(EditPageWorkflow)) + { + return; + } + + var editPageWorkflow = workflow as EditPageWorkflow; + + IPage page = editPageWorkflow.Bindings["SelectedPage"] as IPage; + Guid pageId = page.Id; + + using (var dc = new DataConnection()) + { + var allPageVersions = dc.Get().Where(p => p.Id == pageId).ToList(); + + var versions = allPageVersions. + Select(f => new KeyValuePair(f.VersionId, f.VersionName ?? "")). + ToList(); + + parameters.Bindings[BindingNames.PageVersions] = versions; + parameters.Bindings[BindingNames.PageVersionId] = page.VersionId; + } + + EmbedToolbarActions(workflow); + } + + + private void EmbedToolbarActions(FormsWorkflow workflow) + { + string filePath = PathUtil.Resolve(CustomToolbarMarkupRelativePath); + + string text = C1File.ReadAllText(filePath); + + workflow.SetCustomToolbarDefinition(text); + } + + + [Serializable] + public class VersionChangeHandler + { + public void OpenNewEditingWorkflow(object sender, EventArgs args) + { + var workflow = GetWorkflow((CodeActivity) sender); + + var pageId = ((IPage) workflow.Bindings[BindingNames.SelectedPage]).Id; + var newVersionId = (Guid) workflow.Bindings[BindingNames.PageVersionId]; + + EntityToken targetEntityToken; + using (var dc = new DataConnection()) + { + var targetPage = dc.Get().Single(p => p.Id == pageId && p.VersionId == newVersionId); + + targetEntityToken = targetPage.GetDataEntityToken(); + } + + workflow.ExecuteWorklow(targetEntityToken, typeof(EditPageWorkflow)); + } + + private static FormsWorkflow GetWorkflow(CodeActivity codeActivity) + { + var pointer = codeActivity.Parent; + while(pointer != null && !(pointer is FormsWorkflow)) + { + pointer = pointer.Parent; + } + + return pointer as FormsWorkflow; + } + } + } +} diff --git a/Composite.VersionPublishing/StartupHandler.cs b/Composite.VersionPublishing/StartupHandler.cs index a2e532dd13..05f3fcdc0a 100644 --- a/Composite.VersionPublishing/StartupHandler.cs +++ b/Composite.VersionPublishing/StartupHandler.cs @@ -1,4 +1,5 @@ using Composite.C1Console.Elements; +using Composite.C1Console.Workflow; using Composite.Core.Application; namespace Composite.VersionPublishing @@ -10,6 +11,7 @@ public static void OnBeforeInitialize() { VersionPublishingFacade.RegisterDefaultVersioningService(VersioningServiceSettings.MostRelevant()); UrlToEntityTokenFacade.Register(new VersionedDataUrlToEntityTokenMapper()); + FormsWorkflowExtensions.Register(new EditPageWorkflowExtension()); } public static void OnInitialized() From eb96b7073574ae67bacd8e2b6b8e928c7a28ccf4 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 25 May 2016 12:13:47 +0200 Subject: [PATCH 041/213] Adding AddNewPageVersionWorkflow --- .../Composite.VersionPublishing.csproj | 7 + .../EditPageWorkflowExtension.cs | 53 ++-- .../AddNewPageVersionWorkflow.Designer.cs | 290 ++++++++++++++++++ .../Workflows/AddNewPageVersionWorkflow.cs | 130 ++++++++ 4 files changed, 458 insertions(+), 22 deletions(-) create mode 100644 Composite.VersionPublishing/Workflows/AddNewPageVersionWorkflow.Designer.cs create mode 100644 Composite.VersionPublishing/Workflows/AddNewPageVersionWorkflow.cs diff --git a/Composite.VersionPublishing/Composite.VersionPublishing.csproj b/Composite.VersionPublishing/Composite.VersionPublishing.csproj index 3b7a060d04..5da954642c 100644 --- a/Composite.VersionPublishing/Composite.VersionPublishing.csproj +++ b/Composite.VersionPublishing/Composite.VersionPublishing.csproj @@ -54,6 +54,12 @@ + + Component + + + AddNewPageVersionWorkflow.cs + @@ -65,6 +71,7 @@ Composite + - \ No newline at end of file diff --git a/Composite.VersionPublishing/EditPageWorkflowExtension.cs b/Composite.VersionPublishing/EditPageWorkflowExtension.cs deleted file mode 100644 index 19f35276de..0000000000 --- a/Composite.VersionPublishing/EditPageWorkflowExtension.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Workflow.Activities; -using Composite.C1Console.Security; -using Composite.C1Console.Workflow; -using Composite.C1Console.Workflow.Activities; -using Composite.Core.IO; -using Composite.Data; -using Composite.Data.Types; -using Composite.Plugins.Elements.ElementProviders.MediaFileProviderElementProvider; -using Composite.Plugins.Elements.ElementProviders.PageElementProvider; - -namespace Composite.VersionPublishing -{ - internal class EditPageWorkflowExtension : IFormsWorkflowExtension - { - private static readonly Guid AddNewVersionOptionId = new Guid("46e22dca-c772-44e9-b80b-34fd49fd86bf"); - - private static class BindingNames - { - public const string SelectedPage = "SelectedPage"; - - // Custom - public const string PageVersionId = "PageVersionId"; - public const string PageVersions = "PageVersions"; - } - - const string CustomToolbarMarkupRelativePath = - "~/Composite/InstalledPackages/content/forms/Composite.VersionPublishing/EditPage.ToolBar.xml"; - - const string EditStateActivityName = "editStateActivity"; - - public void Initialize(FormsWorkflow workflow) - { - if (workflow.GetType() != typeof (EditPageWorkflow)) - { - return; - } - - // Creating an EventDrivenActivity to handle version change - var customEvent02Activity = new CustomEvent02HandleExternalEventActivity - { - EventName = "CustomEvent02", - InterfaceType = typeof (IFormsWorkflowEventService), - Name = "customEvent02Activity" - }; - - var codeActivity = new CodeActivity(); - var handler = new EventHandlers(); - codeActivity.ExecuteCode += handler.OpenNewEditingWorkflow; - - var versionChange_EventDrivenActivity = new EventDrivenActivity - { - Name = "eventDrivenActivity_VersionChange", - Activities = - { - customEvent02Activity, - codeActivity, - new SetStateActivity("versionChange_setState") - { - TargetStateName = EditStateActivityName - } - //, new CloseCurrentViewActivity() - } - }; - - - var editStateActivity = workflow.Activities.Single(a => a.Name == EditStateActivityName) - as StateActivity; - - // Detaching "editStateActivity" from the workflow so it can be modified - workflow.Activities.Remove(editStateActivity); - - editStateActivity.Activities.Add(versionChange_EventDrivenActivity); - - workflow.Activities.Add(editStateActivity); - } - - - public void OnDeliverFormData(FormsWorkflow workflow, OnDeliverFormDataParameters parameters) - { - if (workflow.GetType() != typeof(EditPageWorkflow)) - { - return; - } - - var editPageWorkflow = workflow as EditPageWorkflow; - - IPage page = editPageWorkflow.Bindings["SelectedPage"] as IPage; - Guid pageId = page.Id; - - using (var dc = new DataConnection()) - { - var allPageVersions = dc.Get().Where(p => p.Id == pageId).ToList(); - - var versions = allPageVersions - .Select(v => new KeyValuePair(v.VersionId, v.VersionName ?? "")) - .OrderBy(v => v.Value) - .ToList(); - - versions.Insert(0, new KeyValuePair( - AddNewVersionOptionId, - "Add new version")); // TODO: localize - - parameters.Bindings[BindingNames.PageVersions] = versions; - parameters.Bindings[BindingNames.PageVersionId] = page.VersionId; - } - - EmbedToolbarActions(workflow); - } - - - private void EmbedToolbarActions(FormsWorkflow workflow) - { - string filePath = PathUtil.Resolve(CustomToolbarMarkupRelativePath); - - string text = C1File.ReadAllText(filePath); - - workflow.SetCustomToolbarDefinition(text); - } - - - [Serializable] - public class EventHandlers - { - public void OpenNewEditingWorkflow(object sender, EventArgs args) - { - var workflow = ((CodeActivity) sender).GetRoot(); - - var newVersionId = (Guid)workflow.Bindings[BindingNames.PageVersionId]; - var page = (IPage)workflow.Bindings[BindingNames.SelectedPage]; - - // Returning version selector to its previous value - workflow.Bindings[BindingNames.PageVersionId] = page.VersionId; - workflow.RerenderView(); - - if (newVersionId == AddNewVersionOptionId) - { - workflow.ExecuteWorklow(workflow.EntityToken, typeof(AddNewPageVersionWorkflow)); - return; - } - - - EntityToken targetEntityToken; - using (var dc = new DataConnection()) - { - var pageId = page.Id; - var targetPage = dc.Get().Single(p => p.Id == pageId && p.VersionId == newVersionId); - - targetEntityToken = targetPage.GetDataEntityToken(); - } - - workflow.ExecuteWorklow(targetEntityToken, typeof(EditPageWorkflow)); - } - } - } -} diff --git a/Composite.VersionPublishing/IVersionFilteringSettings.cs b/Composite.VersionPublishing/IVersionFilteringSettings.cs deleted file mode 100644 index 02f8c63c41..0000000000 --- a/Composite.VersionPublishing/IVersionFilteringSettings.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace Composite.VersionPublishing -{ - public interface IVersionFilteringSettings - { - VersionFilteringMode FilteringMode { get; } - DateTime? Time { get; } - string VersionName { get; } - } - - public class VersionFilteringSettings : IVersionFilteringSettings - { - public VersionFilteringMode FilteringMode { get; set; } - public DateTime? Time { get; set; } - public string VersionName { get; set; } - } - - public enum VersionFilteringMode - { - /// - /// No filtering should be applied - /// - None = 0, - /// - /// Only currently published data should be returned - /// - Published = 1, - /// - /// Only relevant data should be returned - /// - Relevant = 2 - } -} diff --git a/Composite.VersionPublishing/Properties/AssemblyInfo.cs b/Composite.VersionPublishing/Properties/AssemblyInfo.cs deleted file mode 100644 index cca5525090..0000000000 --- a/Composite.VersionPublishing/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Composite.VersionPublishing")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Composite.VersionPublishing")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("195ea55c-7621-42da-b07e-8b48927c7ad6")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Composite.VersionPublishing/StartupHandler.cs b/Composite.VersionPublishing/StartupHandler.cs deleted file mode 100644 index 05f3fcdc0a..0000000000 --- a/Composite.VersionPublishing/StartupHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Composite.C1Console.Elements; -using Composite.C1Console.Workflow; -using Composite.Core.Application; - -namespace Composite.VersionPublishing -{ - [ApplicationStartup] - public class StartupHandler - { - public static void OnBeforeInitialize() - { - VersionPublishingFacade.RegisterDefaultVersioningService(VersioningServiceSettings.MostRelevant()); - UrlToEntityTokenFacade.Register(new VersionedDataUrlToEntityTokenMapper()); - FormsWorkflowExtensions.Register(new EditPageWorkflowExtension()); - } - - public static void OnInitialized() - { - } - } -} diff --git a/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs b/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs deleted file mode 100644 index 70f1e1bad4..0000000000 --- a/Composite.VersionPublishing/VersionFilteringDataInterceptor.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Linq; -using Composite.Data; -using Composite.Data.Types; - -namespace Composite.VersionPublishing -{ - public class PageVersionFilteringDataInterceptor : DataInterceptor - { - public override IQueryable InterceptGetData(IQueryable query) - { - if (typeof (T) == typeof (IPage)) - { - return FilterPages((IQueryable) query) as IQueryable; - } - - return FilterOtherVersionedPages(query); - - } - - private static IQueryable FilterPages(IQueryable query) - { - using (var dataConnection = new DataConnection()) - { - var settings = dataConnection.GetService(typeof (VersioningServiceSettings)) - as VersioningServiceSettings; - - if (settings == null) - { - return query; - } - - var vfs = settings.VersionFilteringSettings; - - if (vfs.VersionName != null) - { - return query. - Where( - page => - page.VersionName == vfs.VersionName || - ((page.PublishTime ?? DateTime.MinValue) <= DateTime.Now && - (page.UnpublishTime ?? DateTime.MaxValue) >= DateTime.Now)) - .GroupBy(p => p.Id) - .Select( - g => - g.OrderByDescending(page => page.VersionName == vfs.VersionName) - .ThenByDescending(page => page.PublishTime) - .First()); - } - switch (vfs.FilteringMode) - { - case VersionFilteringMode.Published: - return query - .Where(page => (page.PublishTime == null || page.PublishTime <= vfs.Time) - && (page.UnpublishTime == null || page.UnpublishTime >= vfs.Time)) - .GroupBy(p => p.Id) - .Select(g => g.OrderByDescending(page => page.PublishTime ?? DateTime.MinValue).First()); - case VersionFilteringMode.None: - return query.OrderByDescending(page => page.PublishTime ?? DateTime.MinValue); - case VersionFilteringMode.Relevant: - return query - .GroupBy(p => p.Id) - .Select(g => g.OrderByDescending(page => page.PublishTime ?? DateTime.MinValue).First()); - } - - return query; - } - } - - private static IQueryable FilterOtherVersionedPages(IQueryable query) - { - using (var dataConnection = new DataConnection()) - { - var settings = dataConnection.GetService(typeof(VersioningServiceSettings)) - as VersioningServiceSettings; - - if (settings == null) - { - return query; - } - - //var vfs = settings.VersionFilteringSettings; - //var relatedPages = dataConnection.Get().Where(f => f.VersionName == vfs.VersionName); - - var relatedPages = FilterPages(dataConnection.Get()); - - if (relatedPages != null) - { - return (from versioned in query - join page in relatedPages on (versioned as IVersioned).VersionId equals page.VersionId - select versioned) - .Union(from versioned in query - where (versioned as IVersioned).VersionId == Guid.Empty - select versioned); - } - - return query; - } - } - } -} diff --git a/Composite.VersionPublishing/VersionHttpModule.cs b/Composite.VersionPublishing/VersionHttpModule.cs deleted file mode 100644 index 56a41af76e..0000000000 --- a/Composite.VersionPublishing/VersionHttpModule.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Globalization; -using System.Web; -using Composite.Core.Extensions; -using Composite.Core.Routing.Pages; -using Composite.Data; - -namespace Composite.VersionPublishing -{ - class VersionHttpModule : IHttpModule - { - private const string HttpContextItemsKey = nameof(VersionHttpModule) + ":DataScope"; - - private void SetVersioningServiceFromUrlPath(DataScope dataScope, string pathInfo) - { - if (VersionNameUrlHelper.CheckIfAdminUrl(pathInfo)) - { - dataScope.AddService(VersioningServiceSettings.NoFiltering()); - return; - } - - var versionName = VersionNameUrlHelper.ResolveVersionName(pathInfo); - if (!versionName.IsNullOrEmpty()) - { - C1PageRoute.RegisterPathInfoUsage(); - dataScope.AddService(VersioningServiceSettings.ByName(versionName)); - } - } - - public void Init(HttpApplication context) - { - context.BeginRequest += context_BeginRequest; - context.EndRequest += context_EndRequest; - } - - private void context_EndRequest(object sender, EventArgs e) - { - var httpContext = ((HttpApplication)sender).Context; - - var dataScope = httpContext.Items[HttpContextItemsKey] as DataScope; - dataScope?.Dispose(); - } - - private void context_BeginRequest(object sender, EventArgs e) - { - var httpContext = ((HttpApplication)sender).Context; - - var dataScope = new DataScope(null as CultureInfo); - httpContext.Items[HttpContextItemsKey] = dataScope; - - SetVersioningServiceFromUrlPath(dataScope, httpContext.Request.Path); - } - - public void Dispose() - { - } - } -} diff --git a/Composite.VersionPublishing/VersionNameUrlHelper.cs b/Composite.VersionPublishing/VersionNameUrlHelper.cs deleted file mode 100644 index 05f51f52fd..0000000000 --- a/Composite.VersionPublishing/VersionNameUrlHelper.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using Composite.Core.Extensions; -using Composite.Core.WebClient; - -namespace Composite.VersionPublishing -{ - public static class VersionNameUrlHelper - { - const string Pattern = @"\bc1version\b\((.*?)\)"; - private static readonly Regex CompiledRegex = new Regex(Pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); - - public static string ResolveVersionName(string pathInfo) - { - if (pathInfo.IsNullOrEmpty()) - { - return null; - } - - var matches = CompiledRegex.Matches(pathInfo); - if (matches.Count != 1) - { - return null; - } - - var versionString = matches[0].Value; - return versionString.Substring(10, versionString.Length - 11); - } - - public static bool CheckIfAdminUrl(string pathInfo) - { - if (pathInfo.IsNullOrEmpty()) - { - return false; - } - - var adminRootPath = UrlUtils.AdminRootPath; - if (!adminRootPath.EndsWith("/")) - { - adminRootPath = $"{adminRootPath}/"; - } - - return pathInfo.StartsWith(adminRootPath, StringComparison.InvariantCultureIgnoreCase); - } - - public static string VersionNameToUrl(string versionName) - { - return $"/c1version({Uri.EscapeDataString(versionName)})"; - } - } -} \ No newline at end of file diff --git a/Composite.VersionPublishing/VersionPublishingFacade.cs b/Composite.VersionPublishing/VersionPublishingFacade.cs deleted file mode 100644 index 37198b2db8..0000000000 --- a/Composite.VersionPublishing/VersionPublishingFacade.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Composite.Data; - -namespace Composite.VersionPublishing -{ - public static class VersionPublishingFacade - { - public static void RegisterDefaultVersioningService(VersioningServiceSettings versioningServiceSettings) - { - DataScopeServicesFacade.RegisterDefaultService(versioningServiceSettings); - } - } -} diff --git a/Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs b/Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs deleted file mode 100644 index 48e04fa4e8..0000000000 --- a/Composite.VersionPublishing/VersionedDataUrlToEntityTokenMapper.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Linq; -using Composite.C1Console.Elements; -using Composite.C1Console.Security; -using Composite.Core; -using Composite.Core.Extensions; -using Composite.Data; -using Composite.Data.Types; - -namespace Composite.VersionPublishing -{ - class VersionedDataUrlToEntityTokenMapper : IServiceUrlToEntityTokenMapper - { - public string ProcessUrl(string url, EntityToken entityToken) - { - var dataEntityToken = entityToken as DataEntityToken; - - var data = dataEntityToken?.Data; - - var page = data as IPage; - if (page != null) - { - if (!page.VersionName.IsNullOrEmpty()) - { - var urlBuilder = new UrlBuilder(url); - url = urlBuilder.FilePath + VersionNameUrlHelper.VersionNameToUrl(page.VersionName) + - urlBuilder.QueryString; - } - - } - return url; - - } - - public EntityToken TryGetEntityToken(string url, ref EntityToken entityToken) - { - var versionname = VersionNameUrlHelper.ResolveVersionName(url); - var page = (entityToken as DataEntityToken)?.Data as IPage; - if (page != null) - { - using (var dc = new DataConnection()) - { - dc.AddService(VersioningServiceSettings.ByName(versionname)); - var firstOrDefault = dc.Get() - .FirstOrDefault(f => f.Id == page.Id); - if (firstOrDefault != null) - { - var versionId = firstOrDefault.VersionId; - page.VersionId = versionId; - page.VersionName = versionname; - entityToken = page.GetDataEntityToken(); - return entityToken; - } - } - } - return entityToken; - } - - public string CleanUrl(ref string url) - { - return url.Replace(@"/"+ VersionNameUrlHelper.ResolveVersionName(url), ""); - } - - } -} diff --git a/Composite.VersionPublishing/VersioningServiceSettings.cs b/Composite.VersionPublishing/VersioningServiceSettings.cs deleted file mode 100644 index 669c00480f..0000000000 --- a/Composite.VersionPublishing/VersioningServiceSettings.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using Composite.Data; -using Composite.Data.Types; - -namespace Composite.VersionPublishing -{ - public class VersioningServiceSettings - { - private readonly VersionFilteringSettings _versionFilteringSettings; - public VersionFilteringSettings VersionFilteringSettings => _versionFilteringSettings; - - public static VersioningServiceSettings NoFiltering() - { - return new VersioningServiceSettings(VersionFilteringMode.None, DateTime.Now); - } - - public static VersioningServiceSettings Published(DateTime time) - { - return new VersioningServiceSettings(VersionFilteringMode.Published, time); - } - - public static VersioningServiceSettings Published() - { - return new VersioningServiceSettings(VersionFilteringMode.Published, DateTime.Now); - } - - public static VersioningServiceSettings MostRelevant(DateTime time) - { - return new VersioningServiceSettings(VersionFilteringMode.Relevant, time); - } - - public static VersioningServiceSettings MostRelevant() - { - return new VersioningServiceSettings(VersionFilteringMode.Relevant, DateTime.Now); - } - - public static VersioningServiceSettings ByName(string versionName) - { - return new VersioningServiceSettings(versionName); - } - - private VersioningServiceSettings(string versionName) - { - SetupDataInterceptor(); - - _versionFilteringSettings = new VersionFilteringSettings - { - FilteringMode = VersionFilteringMode.Published, - VersionName = versionName - }; - } - - private VersioningServiceSettings(VersionFilteringMode filteringMode, DateTime time) - { - SetupDataInterceptor(); - - _versionFilteringSettings = new VersionFilteringSettings - { - FilteringMode = filteringMode, - Time = time - }; - } - - private void SetupDataInterceptor() - { - if (!DataFacade.HasGlobalDataInterceptor()) - { - DataFacade.SetGlobalDataInterceptor(new PageVersionFilteringDataInterceptor()); - } - } - - public void ChangeProperties(VersionFilteringMode filteringMode, DateTime? time) - { - _versionFilteringSettings.FilteringMode = filteringMode; - if (time != null) - { - _versionFilteringSettings.Time = time; - } - } - - } -} diff --git a/Composite.VersionPublishing/Workflows/AddNewPageVersionWorkflow.Designer.cs b/Composite.VersionPublishing/Workflows/AddNewPageVersionWorkflow.Designer.cs deleted file mode 100644 index 6e55325ab8..0000000000 --- a/Composite.VersionPublishing/Workflows/AddNewPageVersionWorkflow.Designer.cs +++ /dev/null @@ -1,290 +0,0 @@ -using System; -using System.ComponentModel; -using System.ComponentModel.Design; -using System.Collections; -using System.Drawing; -using System.Reflection; -using System.Workflow.ComponentModel.Compiler; -using System.Workflow.ComponentModel.Serialization; -using System.Workflow.ComponentModel; -using System.Workflow.ComponentModel.Design; -using System.Workflow.Runtime; -using System.Workflow.Activities; -using System.Workflow.Activities.Rules; - -namespace Composite.Plugins.Elements.ElementProviders.MediaFileProviderElementProvider -{ - partial class AddNewPageVersionWorkflow - { - #region Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - [System.Diagnostics.DebuggerNonUserCode] - private void InitializeComponent() - { - this.CanModifyActivities = true; - System.Workflow.Activities.CodeCondition codecondition1 = new System.Workflow.Activities.CodeCondition(); - this.setStateActivity3 = new System.Workflow.Activities.SetStateActivity(); - this.setStateActivity6 = new System.Workflow.Activities.SetStateActivity(); - this.ifElseBranchActivity2 = new System.Workflow.Activities.IfElseBranchActivity(); - this.step1IfElseBranchActivity_DoesFolderExist = new System.Workflow.Activities.IfElseBranchActivity(); - this.setStateActivity4 = new System.Workflow.Activities.SetStateActivity(); - this.closeCurrentViewActivity1 = new Composite.C1Console.Workflow.Activities.CloseCurrentViewActivity(); - this.finalizeCodeActivity = new System.Workflow.Activities.CodeActivity(); - this.setStateActivity5 = new System.Workflow.Activities.SetStateActivity(); - this.cancelHandleExternalEventActivity2 = new Composite.C1Console.Workflow.Activities.CancelHandleExternalEventActivity(); - this.ifElseActivity1 = new System.Workflow.Activities.IfElseActivity(); - this.finishHandleExternalEventActivity1 = new Composite.C1Console.Workflow.Activities.FinishHandleExternalEventActivity(); - this.wizzardFormActivity1 = new Composite.C1Console.Workflow.Activities.DataDialogFormActivity(); - this.setStateActivity2 = new System.Workflow.Activities.SetStateActivity(); - this.initializeAddNewfolderCodeActivity = new System.Workflow.Activities.CodeActivity(); - this.setStateActivity1 = new System.Workflow.Activities.SetStateActivity(); - this.cancelHandleExternalEventActivity1 = new Composite.C1Console.Workflow.Activities.CancelHandleExternalEventActivity(); - this.stateInitializationActivity3 = new System.Workflow.Activities.StateInitializationActivity(); - this.step1EventDrivenActivity_Cancel = new System.Workflow.Activities.EventDrivenActivity(); - this.step1EventDrivenActivity_Finish = new System.Workflow.Activities.EventDrivenActivity(); - this.stateInitializationActivity2 = new System.Workflow.Activities.StateInitializationActivity(); - this.stateInitializationActivity1 = new System.Workflow.Activities.StateInitializationActivity(); - this.cancelActivity = new System.Workflow.Activities.EventDrivenActivity(); - this.finalStateActivity = new System.Workflow.Activities.StateActivity(); - this.finalizeStateActivity = new System.Workflow.Activities.StateActivity(); - this.step1StateActivity = new System.Workflow.Activities.StateActivity(); - this.AddNewMediaFolderWorkflowInitialState = new System.Workflow.Activities.StateActivity(); - // - // setStateActivity3 - // - this.setStateActivity3.Name = "setStateActivity3"; - this.setStateActivity3.TargetStateName = "step1StateActivity"; - // - // setStateActivity6 - // - this.setStateActivity6.Name = "setStateActivity6"; - this.setStateActivity6.TargetStateName = "finalizeStateActivity"; - // - // ifElseBranchActivity2 - // - this.ifElseBranchActivity2.Activities.Add(this.setStateActivity3); - this.ifElseBranchActivity2.Name = "ifElseBranchActivity2"; - // - // step1IfElseBranchActivity_DoesFolderExist - // - this.step1IfElseBranchActivity_DoesFolderExist.Activities.Add(this.setStateActivity6); - codecondition1.Condition += new System.EventHandler(this.ValidateInputs); - this.step1IfElseBranchActivity_DoesFolderExist.Condition = codecondition1; - this.step1IfElseBranchActivity_DoesFolderExist.Name = "step1IfElseBranchActivity_DoesFolderExist"; - // - // setStateActivity4 - // - this.setStateActivity4.Name = "setStateActivity4"; - this.setStateActivity4.TargetStateName = "finalStateActivity"; - // - // closeCurrentViewActivity1 - // - this.closeCurrentViewActivity1.Name = "closeCurrentViewActivity1"; - // - // finalizeCodeActivity - // - this.finalizeCodeActivity.Name = "finalizeCodeActivity"; - this.finalizeCodeActivity.ExecuteCode += new System.EventHandler(this.finalizeCodeActivity_ExecuteCode); - // - // setStateActivity5 - // - this.setStateActivity5.Name = "setStateActivity5"; - this.setStateActivity5.TargetStateName = "finalStateActivity"; - // - // cancelHandleExternalEventActivity2 - // - this.cancelHandleExternalEventActivity2.EventName = "Cancel"; - this.cancelHandleExternalEventActivity2.InterfaceType = typeof(Composite.C1Console.Workflow.IFormsWorkflowEventService); - this.cancelHandleExternalEventActivity2.Name = "cancelHandleExternalEventActivity2"; - // - // ifElseActivity1 - // - this.ifElseActivity1.Activities.Add(this.step1IfElseBranchActivity_DoesFolderExist); - this.ifElseActivity1.Activities.Add(this.ifElseBranchActivity2); - this.ifElseActivity1.Name = "ifElseActivity1"; - // - // finishHandleExternalEventActivity1 - // - this.finishHandleExternalEventActivity1.EventName = "Finish"; - this.finishHandleExternalEventActivity1.InterfaceType = typeof(Composite.C1Console.Workflow.IFormsWorkflowEventService); - this.finishHandleExternalEventActivity1.Name = "finishHandleExternalEventActivity1"; - // - // wizzardFormActivity1 - // - this.wizzardFormActivity1.ContainerLabel = null; - this.wizzardFormActivity1.FormDefinitionFileName = "/Composite.VersionPublishing/AddNewPageVersion.xml"; - this.wizzardFormActivity1.Name = "wizzardFormActivity1"; - // - // setStateActivity2 - // - this.setStateActivity2.Name = "setStateActivity2"; - this.setStateActivity2.TargetStateName = "step1StateActivity"; - // - // initializeAddNewfolderCodeActivity - // - this.initializeAddNewfolderCodeActivity.Name = "initializeAddNewfolderCodeActivity"; - this.initializeAddNewfolderCodeActivity.ExecuteCode += new System.EventHandler(this.initializeAddNewfolderCodeActivity_ExecuteCode); - // - // setStateActivity1 - // - this.setStateActivity1.Name = "setStateActivity1"; - this.setStateActivity1.TargetStateName = "finalStateActivity"; - // - // cancelHandleExternalEventActivity1 - // - this.cancelHandleExternalEventActivity1.EventName = "Cancel"; - this.cancelHandleExternalEventActivity1.InterfaceType = typeof(Composite.C1Console.Workflow.IFormsWorkflowEventService); - this.cancelHandleExternalEventActivity1.Name = "cancelHandleExternalEventActivity1"; - // - // stateInitializationActivity3 - // - this.stateInitializationActivity3.Activities.Add(this.finalizeCodeActivity); - this.stateInitializationActivity3.Activities.Add(this.closeCurrentViewActivity1); - this.stateInitializationActivity3.Activities.Add(this.setStateActivity4); - this.stateInitializationActivity3.Name = "stateInitializationActivity3"; - // - // step1EventDrivenActivity_Cancel - // - this.step1EventDrivenActivity_Cancel.Activities.Add(this.cancelHandleExternalEventActivity2); - this.step1EventDrivenActivity_Cancel.Activities.Add(this.setStateActivity5); - this.step1EventDrivenActivity_Cancel.Name = "step1EventDrivenActivity_Cancel"; - // - // step1EventDrivenActivity_Finish - // - this.step1EventDrivenActivity_Finish.Activities.Add(this.finishHandleExternalEventActivity1); - this.step1EventDrivenActivity_Finish.Activities.Add(this.ifElseActivity1); - this.step1EventDrivenActivity_Finish.Name = "step1EventDrivenActivity_Finish"; - // - // stateInitializationActivity2 - // - this.stateInitializationActivity2.Activities.Add(this.wizzardFormActivity1); - this.stateInitializationActivity2.Name = "stateInitializationActivity2"; - // - // stateInitializationActivity1 - // - this.stateInitializationActivity1.Activities.Add(this.initializeAddNewfolderCodeActivity); - this.stateInitializationActivity1.Activities.Add(this.setStateActivity2); - this.stateInitializationActivity1.Name = "stateInitializationActivity1"; - // - // cancelActivity - // - this.cancelActivity.Activities.Add(this.cancelHandleExternalEventActivity1); - this.cancelActivity.Activities.Add(this.setStateActivity1); - this.cancelActivity.Name = "cancelActivity"; - // - // finalStateActivity - // - this.finalStateActivity.Name = "finalStateActivity"; - // - // finalizeStateActivity - // - this.finalizeStateActivity.Activities.Add(this.stateInitializationActivity3); - this.finalizeStateActivity.Name = "finalizeStateActivity"; - // - // step1StateActivity - // - this.step1StateActivity.Activities.Add(this.stateInitializationActivity2); - this.step1StateActivity.Activities.Add(this.step1EventDrivenActivity_Finish); - this.step1StateActivity.Activities.Add(this.step1EventDrivenActivity_Cancel); - this.step1StateActivity.Name = "step1StateActivity"; - // - // AddNewMediaFolderWorkflowInitialState - // - this.AddNewMediaFolderWorkflowInitialState.Activities.Add(this.stateInitializationActivity1); - this.AddNewMediaFolderWorkflowInitialState.Name = "AddNewMediaFolderWorkflowInitialState"; - // - // AddNewMediaFolderWorkflow - // - this.Activities.Add(this.AddNewMediaFolderWorkflowInitialState); - this.Activities.Add(this.step1StateActivity); - this.Activities.Add(this.finalizeStateActivity); - this.Activities.Add(this.finalStateActivity); - this.Activities.Add(this.cancelActivity); - this.CompletedStateName = "finalStateActivity"; - this.DynamicUpdateCondition = null; - this.InitialStateName = "AddNewMediaFolderWorkflowInitialState"; - this.Name = "AddNewMediaFolderWorkflow"; - this.CanModifyActivities = false; - - } - - #endregion - - private CodeActivity initializeAddNewfolderCodeActivity; - - private StateInitializationActivity stateInitializationActivity1; - - private StateActivity finalStateActivity; - - private StateActivity finalizeStateActivity; - - private StateActivity step1StateActivity; - - private StateInitializationActivity stateInitializationActivity3; - - private StateInitializationActivity stateInitializationActivity2; - - private SetStateActivity setStateActivity3; - - private C1Console.Workflow.Activities.DataDialogFormActivity wizzardFormActivity1; - - private SetStateActivity setStateActivity2; - - private SetStateActivity setStateActivity1; - - private C1Console.Workflow.Activities.CancelHandleExternalEventActivity cancelHandleExternalEventActivity1; - - private EventDrivenActivity step1EventDrivenActivity_Finish; - - private EventDrivenActivity cancelActivity; - - private SetStateActivity setStateActivity4; - - private C1Console.Workflow.Activities.FinishHandleExternalEventActivity finishHandleExternalEventActivity1; - - private CodeActivity finalizeCodeActivity; - - private SetStateActivity setStateActivity5; - - private C1Console.Workflow.Activities.CancelHandleExternalEventActivity cancelHandleExternalEventActivity2; - - private EventDrivenActivity step1EventDrivenActivity_Cancel; - - private C1Console.Workflow.Activities.CloseCurrentViewActivity closeCurrentViewActivity1; - - private IfElseBranchActivity ifElseBranchActivity2; - - private IfElseBranchActivity step1IfElseBranchActivity_DoesFolderExist; - - private IfElseActivity ifElseActivity1; - - private SetStateActivity setStateActivity6; - - private StateActivity AddNewMediaFolderWorkflowInitialState; - - - - - - - - - - - - - - - - - - - - - - } -} diff --git a/Composite.VersionPublishing/Workflows/AddNewPageVersionWorkflow.cs b/Composite.VersionPublishing/Workflows/AddNewPageVersionWorkflow.cs deleted file mode 100644 index 86258130ba..0000000000 --- a/Composite.VersionPublishing/Workflows/AddNewPageVersionWorkflow.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Linq; -using System.Workflow.Activities; -using Composite.Data; -using Composite.Data.Types; -using Composite.C1Console.Workflow; -using Composite.Data.ProcessControlled.ProcessControllers.GenericPublishProcessController; -using Composite.Plugins.Elements.ElementProviders.PageElementProvider; - - -namespace Composite.Plugins.Elements.ElementProviders.MediaFileProviderElementProvider -{ - [AllowPersistingWorkflow(WorkflowPersistingType.Idle)] - public sealed partial class AddNewPageVersionWorkflow : Composite.C1Console.Workflow.Activities.FormsWorkflow - { - private static class BindingNames - { - public const string Page = nameof(Page); - public const string VersionName = Page + "." + nameof(ITimedPublishing.VersionName); - public const string PublishTime = Page + "." + nameof(ITimedPublishing.PublishTime); - public const string UnpublishTime = Page + "." + nameof(ITimedPublishing.UnpublishTime); - } - - private IPage CurrentPage => (IPage)Bindings[BindingNames.Page]; - - public AddNewPageVersionWorkflow() - { - InitializeComponent(); - } - - - - private void initializeAddNewfolderCodeActivity_ExecuteCode(object sender, EventArgs e) - { - using (var conn = new DataConnection()) - { - var page = (IPage)((DataEntityToken)EntityToken).Data; - - var newPage = conn.CreateNew(); - newPage.Id = page.Id; - newPage.TemplateId = page.TemplateId; - newPage.PageTypeId = page.PageTypeId; - newPage.Title = page.Title; - newPage.MenuTitle = page.MenuTitle; - newPage.UrlTitle = page.UrlTitle; - newPage.FriendlyUrl = page.FriendlyUrl; - newPage.PublicationStatus = GenericPublishProcessController.Draft; - newPage.Description = page.Description; - - newPage.VersionId = Guid.NewGuid(); - newPage.VersionName = null; - newPage.PublishTime = null; - newPage.UnpublishTime = null; - - Bindings[BindingNames.Page] = newPage; - } - } - - - private void finalizeCodeActivity_ExecuteCode(object sender, EventArgs e) - { - var newPage = CurrentPage; - - using (var conn = new DataConnection()) - { - newPage = conn.Add(newPage); - - // TODO: update nameless version - // TODO: copy the placeholder contents - } - - RefreshParentEntityToken(); - - ExecuteWorklow(newPage.GetDataEntityToken(), typeof(EditPageWorkflow)); - } - - private void ValidateInputs(object sender, ConditionalEventArgs e) - { - e.Result = true; - - var newPage = CurrentPage; - string versionName = newPage.VersionName; - - if (string.IsNullOrEmpty(versionName)) - { - e.Result = false; - // TODO: localize - ShowFieldMessage(BindingNames.VersionName, "This field is required"); - } - else - { - using (var conn = new DataConnection()) - { - Guid pageId = newPage.Id; - - var pageVersions = conn.Get().Where(p => p.Id == pageId).ToList(); - if (pageVersions.Any(p => p.VersionName.Equals(newPage.VersionName, StringComparison.InvariantCultureIgnoreCase))) - { - e.Result = false; - // TODO: localize - ShowFieldMessage(BindingNames.VersionName, "A version with this name already exists"); - } - } - } - - if (newPage.UnpublishTime != null && newPage.UnpublishTime < DateTime.Now) - { - e.Result = false; - // TODO: localize - ShowFieldMessage(BindingNames.UnpublishTime, "The date cannot be set in the past"); - } - - if (newPage.PublishTime != null && newPage.PublishTime < DateTime.Now) - { - e.Result = false; - // TODO: localize - ShowFieldMessage(BindingNames.PublishTime, "The date cannot be set in the past"); - } - - if (newPage.PublishTime != null - && newPage.UnpublishTime != null - && newPage.PublishTime > newPage.UnpublishTime) - { - e.Result = false; - // TODO: localize - ShowFieldMessage(BindingNames.UnpublishTime, "The unpublicaton time should appear later than publication time"); - } - } - } -} \ No newline at end of file From a98e17d36eb3286d233ea9bc00f557f5da78912a Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Fri, 27 May 2016 16:01:52 +0200 Subject: [PATCH 045/213] introducing versionId in entity token as a seprate label. it was included in dataID before --- Composite/C1Console/Security/EntityToken.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Composite/C1Console/Security/EntityToken.cs b/Composite/C1Console/Security/EntityToken.cs index 0fe060227c..235f315ffb 100644 --- a/Composite/C1Console/Security/EntityToken.cs +++ b/Composite/C1Console/Security/EntityToken.cs @@ -164,6 +164,14 @@ public override bool Equals(object obj) { EntityToken entityToken = obj as EntityToken; + return EqualsWithVersionIgnore(entityToken) && entityToken.VersionId == this.VersionId; + } + + /// + public bool EqualsWithVersionIgnore(object obj) + { + EntityToken entityToken = obj as EntityToken; + if (entityToken == null) return false; @@ -177,6 +185,8 @@ public override bool Equals(object obj) entityToken.GetType() == this.GetType(); } + /// + public virtual string VersionId { get; } /// From ffc4676225803bbbc04b55d036c4bb795e11366b Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Fri, 27 May 2016 16:05:20 +0200 Subject: [PATCH 046/213] implementing versionId and intruducing new type of serialization for id and accordingly versionId --- Composite/Data/DataEntityToken.cs | 41 +++++++++++++++++++--------- Composite/Data/DataIdSerializer.cs | 44 ++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/Composite/Data/DataEntityToken.cs b/Composite/Data/DataEntityToken.cs index 6f6b7e70fe..bda91ea082 100644 --- a/Composite/Data/DataEntityToken.cs +++ b/Composite/Data/DataEntityToken.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Reflection; using System.Text; using Composite.C1Console.Security; @@ -17,7 +18,8 @@ public sealed class DataEntityToken : EntityToken private IData _data; private bool _dataInitialized; private string _serializedDataSourceId; - private string _serializedDataId = null; + private string _serializedId = null; + private string _serializedVersionId = null; private string _serializedInterfaceType = null; private Type _interfaceType = null; private DataSourceId _dataSourceId = null; @@ -68,7 +70,7 @@ public override string Type public override string Source { get - { + { return this.DataSourceId.ProviderName; } } @@ -76,12 +78,10 @@ public override string Source /// - public override string Id - { - get { return this.SerializedDataId; } - } - + public override string Id => this.SerializedId; + /// + public override string VersionId => this.SerializedVersionId; /// public override bool IsValid() @@ -184,7 +184,7 @@ public override void OnGetPrettyHtml(EntityTokenHtmlPrettyfier prettyfier) { prettyfier.OnWriteId = (token, helper) => { - IDataId dataId = DataIdSerializer.Deserialize(this.Id); + IDataId dataId = DataIdSerializer.Deserialize(this.Id,this.VersionId); StringBuilder sb = new StringBuilder(); sb.Append("DataId
"); @@ -215,21 +215,36 @@ private string SerializedDataSourceId - private string SerializedDataId + private string SerializedId { get { - if (_serializedDataId == null) + if (_serializedId == null) { - _serializedDataId = this.DataSourceId.DataId.Serialize(); + var keyPropertyNames = this.Data.GetType().GetCustomAttributesRecursively().Select(f=>f.KeyPropertyName); + + _serializedId = this.DataSourceId.DataId.Serialize(keyPropertyNames); } - return _serializedDataId; + return _serializedId; } } + private string SerializedVersionId + { + get + { + if (_serializedVersionId == null) + { + var versionKeyPropertyNames = this.Data.GetType().GetCustomAttributesRecursively().Select(f=>f.VersionKeyPropertyName); + + _serializedVersionId = this.DataSourceId.DataId.Serialize(versionKeyPropertyNames); + } - + return _serializedVersionId; + } + } + private void CheckValidity() { Verify.That(IsValid(), "Failed to deserialize data from serialized data source identifier. Probably the data has been removed from data source."); diff --git a/Composite/Data/DataIdSerializer.cs b/Composite/Data/DataIdSerializer.cs index 2b674deac0..0b2a5fae79 100644 --- a/Composite/Data/DataIdSerializer.cs +++ b/Composite/Data/DataIdSerializer.cs @@ -10,31 +10,53 @@ namespace Composite.Data { internal static class DataIdSerializer { - public static string Serialize(this IDataId dataId) + public static string Serialize(this IDataId dataId,IEnumerable propertyNames) { if (dataId == null) throw new ArgumentNullException("dataId"); StringBuilder sb = new StringBuilder(); StringConversionServices.SerializeKeyValuePair(sb, "_dataIdType_", TypeManager.SerializeType(dataId.GetType())); - StringConversionServices.SerializeKeyValuePair(sb, "_dataId_", SerializationFacade.Serialize(dataId)); + StringConversionServices.SerializeKeyValuePair(sb, "_dataId_", SerializePart(dataId, propertyNames)); return sb.ToString(); } + private static string SerializePart(IDataId dataId, IEnumerable properyNames) + { + StringBuilder sb = new StringBuilder(); + foreach (var properyName in properyNames) + { + StringConversionServices.SerializeKeyValuePair(sb, properyName, dataId?.GetType().GetProperty(properyName)?.GetValue(dataId)); + } + return sb.ToString(); + } - - public static IDataId Deserialize(string serializedDataId) + public static IDataId Deserialize(string serializedId, string serializedVersionId) { - Dictionary dic = StringConversionServices.ParseKeyValueCollection(serializedDataId); + Dictionary dicid = StringConversionServices.ParseKeyValueCollection(serializedId); - if ((dic.ContainsKey("_dataIdType_") == false) || - (dic.ContainsKey("_dataId_") == false)) + if ((dicid.ContainsKey("_dataIdType_") == false) || + (dicid.ContainsKey("_dataId_") == false)) { - throw new ArgumentException("The serializedDataId is not a serialized data id", "serializedDataId"); + throw new ArgumentException("The serializedId is not a serialized id", nameof(serializedId)); + } + + Dictionary dicversion = StringConversionServices.ParseKeyValueCollection(serializedVersionId); + + if ((dicversion.ContainsKey("_dataIdType_") == false) || + (dicversion.ContainsKey("_dataId_") == false)) + { + throw new ArgumentException("The serializedVersionId is not a serialized version id", nameof(serializedVersionId)); + } + + if (dicid["_dataIdType_"] != dicversion["_dataIdType_"]) + { + throw new ArgumentException("Serialized id and version id have diffrent types", nameof(serializedId)); } - string dataIdType = StringConversionServices.DeserializeValueString(dic["_dataIdType_"]); - string serializedDataIdString = StringConversionServices.DeserializeValueString(dic["_dataId_"]); + string dataIdType = StringConversionServices.DeserializeValueString(dicid["_dataIdType_"]); + string serializedIdString = StringConversionServices.DeserializeValueString(dicid["_dataId_"]); + string serializedVersionIdString = StringConversionServices.DeserializeValueString(dicversion["_dataId_"]); Type type = TypeManager.TryGetType(dataIdType); if (type == null) @@ -42,7 +64,7 @@ public static IDataId Deserialize(string serializedDataId) throw new InvalidOperationException(string.Format("The type {0} could not be found", dataIdType)); } - IDataId dataId = SerializationFacade.Deserialize(type, serializedDataIdString); + IDataId dataId = SerializationFacade.Deserialize(type, string.Join("", serializedIdString, serializedVersionIdString)); return dataId; } From 7d2013be59f78bcf7832a22e6dd06fdc236bc273 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Fri, 27 May 2016 16:06:32 +0200 Subject: [PATCH 047/213] changing EntityToken Equality function not to include version in security context --- .../C1Console/Security/PermissionTypeFacade.cs | 14 +++++++------- ...taBasedUserGroupPermissionDefinitionProvider.cs | 4 ++-- .../DataBaseUserPermissionDefinitionProvider.cs | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Composite/C1Console/Security/PermissionTypeFacade.cs b/Composite/C1Console/Security/PermissionTypeFacade.cs index 9cd2a5deff..3bece4bfcc 100644 --- a/Composite/C1Console/Security/PermissionTypeFacade.cs +++ b/Composite/C1Console/Security/PermissionTypeFacade.cs @@ -29,7 +29,7 @@ public static IEnumerable GetLocallyDefinedUserPermissionTypes(U foreach (UserPermissionDefinition userPermissionDefinition in userPermissionDefinitions) { - if (userPermissionDefinition.EntityToken.Equals(entityToken)) + if (userPermissionDefinition.EntityToken.EqualsWithVersionIgnore(entityToken)) { result.AddRange(userPermissionDefinition.PermissionTypes); } @@ -52,7 +52,7 @@ public static IEnumerable GetLocallyDefinedUserGroupPermissionTy foreach (UserGroupPermissionDefinition userGroupPermissionDefinition in userGroupPermissionDefinitions) { - if (userGroupPermissionDefinition.EntityToken.Equals(entityToken)) + if (userGroupPermissionDefinition.EntityToken.EqualsWithVersionIgnore(entityToken)) { result.AddRange(userGroupPermissionDefinition.PermissionTypes); } @@ -551,7 +551,7 @@ private static IReadOnlyCollection RecursiveUpdateCurrentUserPer } UserPermissionDefinition userPermissionDefinition = userPermissionDefinitions - .Where(f => entityToken.Equals(f.EntityToken)).SingleOrDefaultOrException("More then one UserPermissionDefinition for the same entity token"); + .Where(f => entityToken.EqualsWithVersionIgnore(f.EntityToken)).SingleOrDefaultOrException("More then one UserPermissionDefinition for the same entity token"); var thisPermisstionTypes = new List(); if (userPermissionDefinition != null) @@ -611,7 +611,7 @@ private static IReadOnlyCollection RecursiveUpdateCurrentUserGro return cached; } - IEnumerable selectedUserGroupPermissionDefinitions = userGroupPermissionDefinitions.Where(f => entityToken.Equals(f.EntityToken)); + IEnumerable selectedUserGroupPermissionDefinitions = userGroupPermissionDefinitions.Where(f => entityToken.EqualsWithVersionIgnore(f.EntityToken)); List thisPermisstionTypes = new List(); foreach (UserGroupPermissionDefinition userGroupPermissionDefinition in selectedUserGroupPermissionDefinitions) @@ -664,7 +664,7 @@ private static IReadOnlyCollection RecursiveUpdateCurrentUserGro private static IEnumerable GetInheritedGroupPermissionsTypesRecursivly(EntityToken entityToken, IEnumerable userGroupPermissionDefinitions, List visitedParents = null) { - UserGroupPermissionDefinition selectedUserGroupPermissionDefinition = userGroupPermissionDefinitions.Where(f => entityToken.Equals(f.EntityToken)).SingleOrDefault(); + UserGroupPermissionDefinition selectedUserGroupPermissionDefinition = userGroupPermissionDefinitions.Where(f => entityToken.EqualsWithVersionIgnore(f.EntityToken)).SingleOrDefault(); if (selectedUserGroupPermissionDefinition != null) { if (selectedUserGroupPermissionDefinition.PermissionTypes.Contains(PermissionType.ClearPermissions) == false) @@ -728,8 +728,8 @@ public bool Equals(EntityTokenPair entityTokenPair) { if (entityTokenPair == null) return false; - return this.FirstEntityToken.Equals(entityTokenPair.FirstEntityToken) && - this.SecondEntityToken.Equals(entityTokenPair.SecondEntityToken); + return this.FirstEntityToken.EqualsWithVersionIgnore(entityTokenPair.FirstEntityToken) && + this.SecondEntityToken.EqualsWithVersionIgnore(entityTokenPair.SecondEntityToken); } diff --git a/Composite/Plugins/Security/UserGroupPermissionDefinitionProvider/DataBasedUserGroupPermissionDefinitionProvider/DataBasedUserGroupPermissionDefinitionProvider.cs b/Composite/Plugins/Security/UserGroupPermissionDefinitionProvider/DataBasedUserGroupPermissionDefinitionProvider/DataBasedUserGroupPermissionDefinitionProvider.cs index cb7c8d8c04..56e37245fb 100644 --- a/Composite/Plugins/Security/UserGroupPermissionDefinitionProvider/DataBasedUserGroupPermissionDefinitionProvider/DataBasedUserGroupPermissionDefinitionProvider.cs +++ b/Composite/Plugins/Security/UserGroupPermissionDefinitionProvider/DataBasedUserGroupPermissionDefinitionProvider/DataBasedUserGroupPermissionDefinitionProvider.cs @@ -93,7 +93,7 @@ public void SetUserGroupPermissionDefinition(UserGroupPermissionDefinition userG DataFacade.GetData() .Where(d => d.UserGroupId == userGroupId) .ToList() - .Where(d => userGroupPermissionDefinition.EntityToken.Equals(DeserializeSilent(d.SerializedEntityToken))) + .Where(d => userGroupPermissionDefinition.EntityToken.EqualsWithVersionIgnore(DeserializeSilent(d.SerializedEntityToken))) .ToList(); DataFacade.Delete(existingPermissionDefinitions); @@ -135,7 +135,7 @@ public void RemoveUserGroupPermissionDefinition(Guid userGroupId, string seriali toDelete = DataFacade.GetData() .Where(ugpd => ugpd.UserGroupId == userGroupId) .ToList() - .Where(d => entityToken.Equals(DeserializeSilent(d.SerializedEntityToken))) + .Where(d => entityToken.EqualsWithVersionIgnore(DeserializeSilent(d.SerializedEntityToken))) .ToList(); } else diff --git a/Composite/Plugins/Security/UserPermissionDefinitionProvider/DataBaseUserPermissionDefinitionProvider/DataBaseUserPermissionDefinitionProvider.cs b/Composite/Plugins/Security/UserPermissionDefinitionProvider/DataBaseUserPermissionDefinitionProvider/DataBaseUserPermissionDefinitionProvider.cs index 5afa693da2..ed25a29945 100644 --- a/Composite/Plugins/Security/UserPermissionDefinitionProvider/DataBaseUserPermissionDefinitionProvider/DataBaseUserPermissionDefinitionProvider.cs +++ b/Composite/Plugins/Security/UserPermissionDefinitionProvider/DataBaseUserPermissionDefinitionProvider/DataBaseUserPermissionDefinitionProvider.cs @@ -87,7 +87,7 @@ public void SetUserPermissionDefinition(UserPermissionDefinition userPermissionD DataFacade.GetData() .Where(d => d.Username == username) .ToList() - .Where(d => userPermissionDefinition.EntityToken.Equals(DeserializeSilent(d.SerializedEntityToken))) + .Where(d => userPermissionDefinition.EntityToken.EqualsWithVersionIgnore(DeserializeSilent(d.SerializedEntityToken))) .ToList(); DataFacade.Delete(existingUserPermissionDefinitions); @@ -130,7 +130,7 @@ public void RemoveUserPermissionDefinition(UserToken userToken, string serialize toDelete = DataFacade.GetData() .Where(upd => upd.Username == username) .ToList() - .Where(d => entityToken.Equals(DeserializeSilent(d.SerializedEntityToken))) + .Where(d => entityToken.EqualsWithVersionIgnore(DeserializeSilent(d.SerializedEntityToken))) .ToList(); } else From ac990fb9ab32c067b980fc1638145fca1e763adb Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 27 May 2016 17:26:59 +0200 Subject: [PATCH 048/213] Making more methods public --- Composite/C1Console/Workflow/Activities/FormsWorkflow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs index c6aefa4506..5b50d44b18 100644 --- a/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs +++ b/Composite/C1Console/Workflow/Activities/FormsWorkflow.cs @@ -582,7 +582,7 @@ protected IEnumerable GetConsoleIdsOpenedByCurrentUser() /// - protected void ExecuteAction(EntityToken entityToken, ActionToken actionToken) + public void ExecuteAction(EntityToken entityToken, ActionToken actionToken) { var flowControllerServicesContainer = GetFlowControllerServicesContainer(); From 50984d5084f719713404cd8e493517b58b0e5159 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 30 May 2016 10:14:03 +0200 Subject: [PATCH 049/213] Adding "Required" attribute to form control --- .../CoreUiControls/DateSelectorUiControl.cs | 9 +++-- ...mplatedDateTimeSelectorUiControlFactory.cs | 35 ++++++++++--------- .../DateTimeSelectors/DateSelector.ascx | 6 ++-- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Composite/C1Console/Forms/CoreUiControls/DateSelectorUiControl.cs b/Composite/C1Console/Forms/CoreUiControls/DateSelectorUiControl.cs index 1209b4ef1e..1c56c2aa78 100644 --- a/Composite/C1Console/Forms/CoreUiControls/DateSelectorUiControl.cs +++ b/Composite/C1Console/Forms/CoreUiControls/DateSelectorUiControl.cs @@ -8,11 +8,14 @@ namespace Composite.C1Console.Forms.CoreUiControls [ControlValueProperty("Date")] internal abstract class DateTimeSelectorUiControl : UiControl { - [BindableProperty()] - [FormsProperty()] + [BindableProperty] + [FormsProperty] public DateTime? Date { get; set; } - [FormsProperty()] + [FormsProperty] public bool ReadOnly { get; set; } + + [FormsProperty] + public bool Required { get; set; } } } diff --git a/Composite/Plugins/Forms/WebChannel/UiControlFactories/TemplatedDateTimeSelectorUiControlFactory.cs b/Composite/Plugins/Forms/WebChannel/UiControlFactories/TemplatedDateTimeSelectorUiControlFactory.cs index 50124a8fe5..0421169611 100644 --- a/Composite/Plugins/Forms/WebChannel/UiControlFactories/TemplatedDateTimeSelectorUiControlFactory.cs +++ b/Composite/Plugins/Forms/WebChannel/UiControlFactories/TemplatedDateTimeSelectorUiControlFactory.cs @@ -76,6 +76,13 @@ public bool ShowHours set; } + /// + public bool Required + { + get; + set; + } + /// public string FormControlLabel { @@ -88,7 +95,7 @@ public string FormControlLabel internal sealed class TemplatedDateTimeSelectorUiControl : DateTimeSelectorUiControl, IWebUiControl, IValidatingUiControl { - private Type _userControlType; + private readonly Type _userControlType; private DateTimeSelectorTemplateUserControlBase _userControl; internal TemplatedDateTimeSelectorUiControl(Type userControlType) @@ -117,35 +124,30 @@ public Control BuildWebControl() _userControl.ReadOnly = this.ReadOnly; _userControl.ShowHours = this.ShowHours; _userControl.IsValid = true; + _userControl.Required = this.Required; return _userControl; } - public bool IsFullWidthControl { get { return false; } } + public bool IsFullWidthControl => false; - public string ClientName { get { return _userControl.GetDataFieldClientName(); } } + public string ClientName => _userControl.GetDataFieldClientName(); - public bool IsValid - { - get { return _userControl.IsValid; } - } + public bool IsValid => _userControl.IsValid; public bool ShowHours { get; set; } - public string ValidationError - { - get { return _userControl.ValidationError; } - } + public string ValidationError => _userControl.ValidationError; } [ConfigurationElementType(typeof(TemplatedDateTimeSelectorUiControlFactoryData))] internal sealed class TemplatedDateTimeSelectorUiControlFactory : Base.BaseTemplatedUiControlFactory { - private bool _showHours; + private readonly bool _showHours; public TemplatedDateTimeSelectorUiControlFactory(TemplatedDateTimeSelectorUiControlFactoryData data) : base(data) @@ -155,11 +157,10 @@ public TemplatedDateTimeSelectorUiControlFactory(TemplatedDateTimeSelectorUiCont public override IUiControl CreateControl() { - TemplatedDateTimeSelectorUiControl control = new TemplatedDateTimeSelectorUiControl(this.UserControlType); - - control.ShowHours = _showHours; - - return control; + return new TemplatedDateTimeSelectorUiControl(this.UserControlType) + { + ShowHours = _showHours + }; } } diff --git a/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx b/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx index 99dc370402..2260076633 100644 --- a/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx +++ b/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx @@ -3,7 +3,9 @@
+ image="${icon:calendar-full}" value="<%= this.CurrentStringValue %>" + readonly="<%=this.ReadOnly.ToString().ToLower()%>" + required="<%=this.Required.ToString().ToLower()%>" />
@@ -21,4 +23,4 @@ runat="server">forward
-
+ \ No newline at end of file From f4cd909f8119a11a7ed6d58f7d3941ea7bc3efd1 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Mon, 30 May 2016 16:16:27 +0300 Subject: [PATCH 050/213] Version selectors --- Website/Composite/CompileScripts.xml | 1 + .../views/browser/BrowserPageBinding.js | 2 +- .../content/views/browser/browser.css | 3 + .../combobutton/SelectorButtonBinding.js | 108 ++++++++++++++++++ .../styles/default/fields/selectors.less | 9 ++ .../default/toolbars/document-toolbar.less | 4 +- Website/WebSite.csproj | 1 + 7 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 Website/Composite/scripts/source/top/ui/bindings/buttons/combobutton/SelectorButtonBinding.js diff --git a/Website/Composite/CompileScripts.xml b/Website/Composite/CompileScripts.xml index f980570ce8..019872159c 100644 --- a/Website/Composite/CompileScripts.xml +++ b/Website/Composite/CompileScripts.xml @@ -361,6 +361,7 @@ - + diff --git a/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js b/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js index ec4b23f94e..bb7ae53e13 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js @@ -91,11 +91,11 @@ DataInputSelectorBinding.prototype.onBindingDispose = SelectorBinding.prototype. */ DataInputSelectorBinding.prototype._buildDOMContent = function () { + DataInputSelectorBinding.superclass._buildDOMContent.call(this); + this.buildButton(); this.buildPopup(); this.buildSelections(); - - DataInputSelectorBinding.superclass._buildDOMContent.call ( this ); } @@ -111,13 +111,6 @@ DataInputSelectorBinding.prototype.onBindingAttach = function () { this.setImage(image); } - var onchange = this.getProperty("onchange"); - if (onchange) { - this.onValueChange = function () { - Binding.evaluate(onchange, this); - }; - } - var self = this; DOMEvents.addEventListener(this.shadowTree.input, DOMEvents.DOUBLECLICK, { handleEvent: function (e) { @@ -431,7 +424,10 @@ DataInputSelectorBinding.prototype.select = function ( item, isDefault ) { if ( !isDefault ) { this.dirty(); - this.onValueChange(); + var onselectionchange = this.getProperty("onselectionchange"); + if (onselectionchange) { + Binding.evaluate(onselectionchange, this); + } this.dispatchAction ( DataInputSelectorBinding.ACTION_SELECTIONCHANGED ); From 8680780e14aef88ef099ad16b07b8c288057ffb9 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Thu, 23 Jun 2016 13:39:22 +0200 Subject: [PATCH 086/213] using physical key when getting data bu unique data Id --- Composite/Data/DataFacade.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Composite/Data/DataFacade.cs b/Composite/Data/DataFacade.cs index 913b3af45d..0c7fa32cb8 100644 --- a/Composite/Data/DataFacade.cs +++ b/Composite/Data/DataFacade.cs @@ -489,7 +489,7 @@ public static LambdaExpression GetPredicateExpressionByUniqueKey(Type interfaceT if (interfaceType == null) throw new ArgumentNullException("interfaceType"); if (dataKeyPropertyCollection == null) throw new ArgumentNullException("dataKeyPropertyCollection"); - var keyProperties = DataAttributeFacade.GetKeyProperties(interfaceType); + var keyProperties = DataAttributeFacade.GetPhysicalKeyProperties(interfaceType); ParameterExpression parameterExpression = Expression.Parameter(interfaceType, "data"); @@ -510,10 +510,13 @@ public static LambdaExpression GetPredicateExpressionByUniqueKey(Type interfaceT { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - PropertyInfo propertyInfo = DataAttributeFacade.GetKeyProperties(interfaceType).Single(); - + var propertyInfos = DataAttributeFacade.GetPhysicalKeyProperties(interfaceType); var dataKeyPropertyCollection = new DataKeyPropertyCollection(); - dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); + foreach (var propertyInfo in propertyInfos) + { + dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); + } + return GetPredicateExpressionByUniqueKey(interfaceType, dataKeyPropertyCollection); } @@ -607,10 +610,14 @@ public static IData TryGetDataByUniqueKey(Type interfaceType, object dataKeyValu } - PropertyInfo propertyInfo = DataAttributeFacade.GetKeyProperties(interfaceType).Single(); + var propertyInfos = DataAttributeFacade.GetPhysicalKeyProperties(interfaceType); DataKeyPropertyCollection dataKeyPropertyCollection = new DataKeyPropertyCollection(); - dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); + foreach (var propertyInfo in propertyInfos) + { + dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); + } + return TryGetDataByUniqueKey(interfaceType, dataKeyPropertyCollection); } From 9ccce935cb9cb84ce86b190b85e8d360191bfa2b Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 23 Jun 2016 15:27:06 +0200 Subject: [PATCH 087/213] Extracting logic for deleting pages from DeletePageWorkflow into PaseServices --- .../PageElementProvider/DeletePageWorkflow.cs | 114 ++++-------------- Composite/Data/Types/PageServices.cs | 112 +++++++++++++++-- 2 files changed, 125 insertions(+), 101 deletions(-) diff --git a/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/DeletePageWorkflow.cs b/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/DeletePageWorkflow.cs index d01bca5de5..2794321e10 100644 --- a/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/DeletePageWorkflow.cs +++ b/Composite.Workflows/Plugins/Elements/ElementProviders/PageElementProvider/DeletePageWorkflow.cs @@ -115,95 +115,36 @@ private void codeActivity2_ExecuteCode(object sender, EventArgs e) DataEntityToken dataEntityToken = (DataEntityToken)this.EntityToken; IPage selectedPage = (IPage)dataEntityToken.Data; - using (var transactionScope = TransactionsFacade.CreateNewScope()) - { - if (!DataFacade.WillDeleteSucceed(selectedPage)) - { - this.ShowMessage( - DialogType.Error, - Texts.DeletePageWorkflow_CascadeDeleteErrorTitle, - Texts.DeletePageWorkflow_CascadeDeleteErrorMessage - ); - - return; - } - - List cultures = DataLocalizationFacade.ActiveLocalizationCultures.ToList(); - cultures.Remove(selectedPage.DataSourceId.LocaleScope); - - if (hasSubPages) - { - List pagesToDelete = selectedPage.GetSubChildren().ToList(); - - if (pagesToDelete.Any(page => !DataFacade.WillDeleteSucceed(page))) - { - this.ShowMessage(DialogType.Error, - Texts.DeletePageWorkflow_CascadeDeleteErrorTitle, - Texts.DeletePageWorkflow_CascadeDeleteErrorMessage); - - return; - } - foreach (IPage page in pagesToDelete) - { - if (!ExistInOtherLocale(cultures, page)) - { - RemoveAllFolderAndMetaDataDefinitions(page); - } - - page.DeletePageStructure(); - ProcessControllerFacade.FullDelete(page); - } - } - - if (!ExistInOtherLocale(cultures, selectedPage)) - { - RemoveAllFolderAndMetaDataDefinitions(selectedPage); - } - - var parentTreeRefresher = this.CreateParentTreeRefresher(); - parentTreeRefresher.PostRefreshMessages(selectedPage.GetDataEntityToken(), 2); - - selectedPage.DeletePageStructure(); - - Guid pageId = selectedPage.Id; - var pageVersions = DataFacade.GetData(p => p.Id == pageId).ToList(); - - ProcessControllerFacade.FullDelete(pageVersions); + if (!DataFacade.WillDeleteSucceed(selectedPage)) + { + this.ShowMessage( + DialogType.Error, + Texts.DeletePageWorkflow_CascadeDeleteErrorTitle, + Texts.DeletePageWorkflow_CascadeDeleteErrorMessage + ); - transactionScope.Complete(); + return; } - } - - private bool ExistInOtherLocale(List cultures, IPage page) - { - foreach (CultureInfo localeCultureInfo in cultures) + if (hasSubPages) { - using (new DataScope(localeCultureInfo)) + List pagesToDelete = selectedPage.GetSubChildren().ToList(); + + if (pagesToDelete.Any(page => !DataFacade.WillDeleteSucceed(page))) { - if (Composite.Data.PageManager.GetPageById(page.Id) != null) - { - return true; - } + this.ShowMessage(DialogType.Error, + Texts.DeletePageWorkflow_CascadeDeleteErrorTitle, + Texts.DeletePageWorkflow_CascadeDeleteErrorMessage); + + return; } } - return false; - } - - - private void RemoveAllFolderAndMetaDataDefinitions(IPage page) - { - foreach (Type folderType in page.GetDefinedFolderTypes()) - { - page.RemoveFolderDefinition(folderType, true); - } + var parentTreeRefresher = this.CreateParentTreeRefresher(); + parentTreeRefresher.PostRefreshMessages(selectedPage.GetDataEntityToken(), 2); - foreach (Tuple metaDataTypeAndName in page.GetDefinedMetaDataTypeAndNames()) - { - page.RemoveMetaDataDefinition(metaDataTypeAndName.Item2, true); - } + PageServices.DeletePage(selectedPage); } @@ -241,20 +182,7 @@ private void DeleteCurrentVersion(object sender, EventArgs e) var dataEntityToken = (DataEntityToken)this.EntityToken; IPage selectedPage = (IPage)dataEntityToken.Data; - Guid pageId = selectedPage.Id; - Guid versionId = selectedPage.VersionId; - - using (var conn = new DataConnection()) - using (var scope = TransactionsFacade.CreateNewScope()) - { - var pageToDelete = conn.Get().Single(p => p.Id == pageId && p.VersionId == versionId); - var placeholders = conn.Get().Where(p => p.PageId == pageId && p.VersionId == versionId).ToList(); - - DataFacade.Delete(placeholders, false, false); - DataFacade.Delete(pageToDelete); - - scope.Complete(); - } + PageServices.DeletePage(selectedPage.Id, selectedPage.VersionId, selectedPage.DataSourceId.LocaleScope); var parentTreeRefresher = this.CreateParentTreeRefresher(); parentTreeRefresher.PostRefreshMessages(selectedPage.GetDataEntityToken(), 2); diff --git a/Composite/Data/Types/PageServices.cs b/Composite/Data/Types/PageServices.cs index 05a8de666e..21d2da1526 100644 --- a/Composite/Data/Types/PageServices.cs +++ b/Composite/Data/Types/PageServices.cs @@ -5,6 +5,8 @@ using Composite.C1Console.Trees; using Composite.C1Console.Users; using Composite.Core.Linq; +using Composite.Data.ProcessControlled; +using Composite.Data.Transactions; namespace Composite.Data.Types @@ -57,10 +59,10 @@ public static IQueryable GetChildren(this IPage page) public static IQueryable GetChildren(Guid parentId) { return from ps in DataFacade.GetData() - join p in DataFacade.GetData() on ps.Id equals p.Id - where ps.ParentId == parentId - orderby ps.LocalOrdering - select p; + join p in DataFacade.GetData() on ps.Id equals p.Id + where ps.ParentId == parentId + orderby ps.LocalOrdering + select p; #warning revisit this - we return all versions (by design so far). Any ordering on page versions? - check history for original intent } @@ -298,10 +300,10 @@ public static IPage InsertIntoPosition(this IPage newPage, Guid parentId, int lo internal static void InsertIntoPositionInternal(Guid newPageId, Guid parentId, int localOrder) { List pageStructures = - (from ps in DataFacade.GetData(false) - where ps.ParentId == parentId - orderby ps.LocalOrdering - select ps).ToList(); + (from ps in DataFacade.GetData(false) + where ps.ParentId == parentId + orderby ps.LocalOrdering + select ps).ToList(); var toBeUpdated = new List(); for (int i = 0; i < pageStructures.Count; i++) @@ -480,6 +482,100 @@ public static void AddPageTypeRelatedData(IPage page) AddPageTypePageFoldersAndApplications(page); } + /// + /// Deletes the versions of the given page in its current localization scope. + /// + public static void DeletePage(IPage page) + { + using (var transactionScope = TransactionsFacade.CreateNewScope()) + { + List cultures = DataLocalizationFacade.ActiveLocalizationCultures.ToList(); + cultures.Remove(page.DataSourceId.LocaleScope); + + List pagesToDelete = page.GetSubChildren().ToList(); + + foreach (IPage childPage in pagesToDelete) + { + if (!ExistInOtherLocale(cultures, childPage)) + { + RemoveAllFolderAndMetaDataDefinitions(childPage); + } + + childPage.DeletePageStructure(); + ProcessControllerFacade.FullDelete(childPage); + } + + + if (!ExistInOtherLocale(cultures, page)) + { + RemoveAllFolderAndMetaDataDefinitions(page); + } + + page.DeletePageStructure(); + + Guid pageId = page.Id; + var pageVersions = DataFacade.GetData(p => p.Id == pageId).ToList(); + + ProcessControllerFacade.FullDelete(pageVersions); + + transactionScope.Complete(); + } + } + + private static bool ExistInOtherLocale(List cultures, IPage page) + { + foreach (CultureInfo localeCultureInfo in cultures) + { + using (new DataScope(localeCultureInfo)) + { + if (Composite.Data.PageManager.GetPageById(page.Id) != null) + { + return true; + } + } + } + + return false; + } + + + private static void RemoveAllFolderAndMetaDataDefinitions(IPage page) + { + foreach (Type folderType in page.GetDefinedFolderTypes()) + { + page.RemoveFolderDefinition(folderType, true); + } + + foreach (Tuple metaDataTypeAndName in page.GetDefinedMetaDataTypeAndNames()) + { + page.RemoveMetaDataDefinition(metaDataTypeAndName.Item2, true); + } + } + + /// + /// Delete the specific version of the page in the current localization scope. + /// + /// + /// + public static void DeletePage(Guid pageId, Guid versionId, CultureInfo locale) + { + using (var conn = new DataConnection(locale)) + using (var scope = TransactionsFacade.CreateNewScope()) + { + var pageToDelete = conn.Get() + .Single(p => p.Id == pageId && p.VersionId == versionId); + + var placeholders = conn.Get() + .Where(p => p.PageId == pageId && p.VersionId == versionId).ToList(); + + DataFacade.Delete(placeholders, false, false); + DataFacade.Delete(pageToDelete); + + scope.Complete(); + } + } + + internal static bool AddPageTypePageFoldersAndApplications(IPage page) { From c16f4c29f67aea8adeafed50afd61c081e184f94 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Fri, 24 Jun 2016 12:09:29 +0200 Subject: [PATCH 088/213] Update UI for DataInputSelectorBinding --- .../ui/bindings/data/selectors/DataInputSelectorBinding.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js b/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js index bb7ae53e13..223aa79381 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js @@ -131,7 +131,7 @@ DataInputSelectorBinding.prototype.buildButton = function () { var button = this.addFirst ( ToolBarButtonBinding.newInstance ( this.bindingDocument ) ); - button.popupBindingTargetElement = this.shadowTree.input; + button.popupBindingTargetElement = this.shadowTree.box; button.setImage ( DataInputSelectorBinding.INDICATOR_IMAGE ); button.attach (); @@ -174,9 +174,8 @@ DataInputSelectorBinding.prototype.buildSelections = function () { }); } }); - if ( list.hasEntries ()) { - this.populateFromList ( list ); - } + + this.populateFromList ( list ); } /** From db8b780f95569f258cf36b43f31b9661a951099c Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 24 Jun 2016 15:15:15 +0200 Subject: [PATCH 089/213] Fixing versioned data migration to SqlDataProvider --- .../Data/DynamicTypes/DataTypeDescriptor.cs | 8 ++- .../CodeGeneration/EntityClassGenerator.cs | 31 +++++------ .../SqlDataProviderHelperGenerator.cs | 50 +++++++++-------- .../SqlDataProviderStoreManipulator.cs | 4 +- .../Sql/SqlColumnInformation.cs | 53 ++++++------------- 5 files changed, 65 insertions(+), 81 deletions(-) diff --git a/Composite/Data/DynamicTypes/DataTypeDescriptor.cs b/Composite/Data/DynamicTypes/DataTypeDescriptor.cs index f5b1403b1e..b44c9935ab 100644 --- a/Composite/Data/DynamicTypes/DataTypeDescriptor.cs +++ b/Composite/Data/DynamicTypes/DataTypeDescriptor.cs @@ -130,6 +130,12 @@ public Guid DataTypeId ///
public DataFieldNameCollection VersionKeyPropertyNames { get; set; } + + /// + /// Version keys, appear in the physical order but not included in data references. + /// + internal IEnumerable PhysicalKeyPropertyNames => KeyPropertyNames.Concat(VersionKeyPropertyNames); + /// /// Returns the CLT Type for this data type description. /// @@ -175,7 +181,7 @@ internal IEnumerable PhysicalKeyFields this.Fields.Where(field => field.Name == fieldName) .SingleOrException("Missing a field '{0}'", "Multiple fields with name '{0}'", fieldName); - return this.KeyPropertyNames.Concat(VersionKeyPropertyNames).Select(getField); + return PhysicalKeyPropertyNames.Select(getField); } } diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityClassGenerator.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityClassGenerator.cs index dff5b2cc0a..0f4a54c71e 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityClassGenerator.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/EntityClassGenerator.cs @@ -222,24 +222,21 @@ private void AddPropertiesAddProperty(CodeTypeDeclaration declaration, string pr propertyMember.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(DebuggerNonUserCodeAttribute)))); - var codeAttributeArguments = new List { - new CodeAttributeArgument("Name", new CodePrimitiveExpression(dbName)), - new CodeAttributeArgument("Storage", new CodePrimitiveExpression(fieldName)), - new CodeAttributeArgument("DbType", new CodePrimitiveExpression(dbType)), - new CodeAttributeArgument("CanBeNull", new CodePrimitiveExpression(isNullable)), - new CodeAttributeArgument("IsPrimaryKey", new CodePrimitiveExpression(isId)), - new CodeAttributeArgument("IsDbGenerated", new CodePrimitiveExpression(isAutoGen)) - }; - - - codeAttributeArguments.Add( - new CodeAttributeArgument("UpdateCheck", - new CodeFieldReferenceExpression( - new CodeTypeReferenceExpression(typeof(UpdateCheck)), - "Never" - ) + var codeAttributeArguments = new List + { + new CodeAttributeArgument("Name", new CodePrimitiveExpression(dbName)), + new CodeAttributeArgument("Storage", new CodePrimitiveExpression(fieldName)), + new CodeAttributeArgument("DbType", new CodePrimitiveExpression(dbType)), + new CodeAttributeArgument("CanBeNull", new CodePrimitiveExpression(isNullable)), + new CodeAttributeArgument("IsPrimaryKey", new CodePrimitiveExpression(isId)), + new CodeAttributeArgument("IsDbGenerated", new CodePrimitiveExpression(isAutoGen)), + new CodeAttributeArgument("UpdateCheck", + new CodeFieldReferenceExpression( + new CodeTypeReferenceExpression(typeof (UpdateCheck)), nameof(UpdateCheck.Never)) ) - ); + }; + + propertyMember.CustomAttributes.Add(new CodeAttributeDeclaration( new CodeTypeReference(typeof(ColumnAttribute)), diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/SqlDataProviderHelperGenerator.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/SqlDataProviderHelperGenerator.cs index eefcc5b055..8b3b88c9ed 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/SqlDataProviderHelperGenerator.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/CodeGeneration/SqlDataProviderHelperGenerator.cs @@ -35,28 +35,32 @@ public SqlDataProviderHelperGenerator(DataTypeDescriptor dataTypeDescriptor, str public CodeTypeDeclaration CreateClass() { - CodeTypeDeclaration declaration = new CodeTypeDeclaration(_sqlDataProviderHelperClassName); + var declaration = new CodeTypeDeclaration(_sqlDataProviderHelperClassName) + { + IsClass = true, + TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed + }; - declaration.IsClass = true; - declaration.TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed; declaration.BaseTypes.Add(typeof(ISqlDataProviderHelper)); - foreach (string keyFieldName in _dataTypeDescriptor.KeyPropertyNames) + foreach (string keyFieldName in _dataTypeDescriptor.PhysicalKeyPropertyNames) { string fieldName = CreateDataIdPropertyInfoFieldName(keyFieldName); - CodeMemberField codeField = new CodeMemberField(new CodeTypeReference(typeof(PropertyInfo)), fieldName); - codeField.InitExpression = - new CodeMethodInvokeExpression( + CodeMemberField codeField = new CodeMemberField(new CodeTypeReference(typeof (PropertyInfo)), fieldName) + { + InitExpression = new CodeMethodInvokeExpression( new CodeMethodReferenceExpression( - new CodeTypeReferenceExpression(typeof(IDataExtensions)), - "GetDataPropertyRecursivly" + new CodeTypeReferenceExpression(typeof (IDataExtensions)), + nameof(IDataExtensions.GetDataPropertyRecursively) ), - new CodeExpression[] { - new CodeTypeOfExpression(_entityClassName), - new CodePrimitiveExpression(keyFieldName) - } - ); + new CodeExpression[] + { + new CodeTypeOfExpression(_entityClassName), + new CodePrimitiveExpression(keyFieldName) + } + ) + }; declaration.Members.Add(codeField); } @@ -133,8 +137,8 @@ private void AddGetDataByIdMethod(CodeTypeDeclaration declaration) // where body variable - CodeExpression currentExpresion = null; - foreach (string propertyName in _dataTypeDescriptor.KeyPropertyNames) + CodeExpression currentExpression = null; + foreach (string propertyName in _dataTypeDescriptor.PhysicalKeyPropertyNames) { CodeExpression newExpression = new CodeMethodInvokeExpression( new CodeTypeReferenceExpression(typeof(Expression)), @@ -164,17 +168,17 @@ private void AddGetDataByIdMethod(CodeTypeDeclaration declaration) } ); - if (currentExpresion == null) + if (currentExpression == null) { - currentExpresion = newExpression; + currentExpression = newExpression; } else { - currentExpresion = new CodeMethodInvokeExpression( + currentExpression = new CodeMethodInvokeExpression( new CodeTypeReferenceExpression(typeof(Expression)), "And", new [] { - currentExpresion, + currentExpression, newExpression } ); @@ -185,11 +189,11 @@ private void AddGetDataByIdMethod(CodeTypeDeclaration declaration) new CodeVariableDeclarationStatement( new CodeTypeReference(typeof(Expression)), whereBodyExpressionVariableName, - currentExpresion + currentExpression )); - // where labmda variable + // where lambda variable codeMethod.Statements.Add( new CodeVariableDeclarationStatement( new CodeTypeReference(typeof(LambdaExpression)), @@ -429,7 +433,7 @@ private void AddRemoveDataMethod(CodeTypeDeclaration declaration) private static string CreateDataIdPropertyInfoFieldName(string propertyName) { - return string.Format("_dataIdEntityPropertyInfo{0}", propertyName); + return $"_dataIdEntityPropertyInfo{propertyName}"; } } } diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/SqlDataProviderStoreManipulator.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/SqlDataProviderStoreManipulator.cs index 895b312df9..78865d58bb 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/SqlDataProviderStoreManipulator.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/SqlDataProviderStoreManipulator.cs @@ -105,7 +105,7 @@ internal void CreateStore(DataTypeDescriptor typeDescriptor, DataScopeIdentifier ).ToList(); sql.AppendFormat("CREATE TABLE dbo.[{0}]({1});", tableName, string.Join(",", sqlColumns)); - sql.Append(SetPrimaryKey(tableName, typeDescriptor.KeyPropertyNames, typeDescriptor.PrimaryKeyIsClusteredIndex)); + sql.Append(SetPrimaryKey(tableName, typeDescriptor.PhysicalKeyPropertyNames, typeDescriptor.PrimaryKeyIsClusteredIndex)); try { @@ -464,7 +464,7 @@ private void AlterStore(UpdateDataTypeDescriptor updateDataTypeDescriptor, DataT bool isClusteredIndex = changeDescriptor.AlteredType.PrimaryKeyIsClusteredIndex; createIndexActions.Add(new Tuple(isClusteredIndex, - () => ExecuteNonQuery(SetPrimaryKey(alteredTableName, changeDescriptor.AlteredType.KeyPropertyNames, isClusteredIndex)) + () => ExecuteNonQuery(SetPrimaryKey(alteredTableName, changeDescriptor.AlteredType.PhysicalKeyPropertyNames, isClusteredIndex)) )); } diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Sql/SqlColumnInformation.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Sql/SqlColumnInformation.cs index 7bd09b43d0..db3973a480 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Sql/SqlColumnInformation.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Sql/SqlColumnInformation.cs @@ -1,6 +1,7 @@ using System; using System.Data; using System.ComponentModel; +using System.Linq; using Composite.Data.DynamicTypes; using Composite.Plugins.Data.DataProviders.MSSqlServerDataProvider.Foundation; @@ -15,7 +16,7 @@ public static SqlColumnInformation CreateSqlColumnInformation(this DataTypeDescr return new SqlColumnInformation( dataFieldDescriptor.Name, - dataTypeDescriptor.KeyPropertyNames.Contains(dataFieldDescriptor.Name), + dataTypeDescriptor.PhysicalKeyPropertyNames.Contains(dataFieldDescriptor.Name), false, false, dataFieldDescriptor.IsNullable, @@ -42,7 +43,7 @@ public sealed class SqlColumnInformation private readonly Type _type; private readonly SqlDbType _sqlDbType; - private int? _hasCode = null; + private int? _hashCode; internal SqlColumnInformation( string columnName, @@ -54,7 +55,7 @@ internal SqlColumnInformation( SqlDbType sqlDbType) { _columnName = columnName; - _trimmedColumnName = _columnName.Replace(" ", ""); + _trimmedColumnName = _columnName.Replace(" ", ""); _isPrimaryKey = isPrimaryKey; _isIdentity = isIdentity; _isComputed = isComputed; @@ -65,67 +66,43 @@ internal SqlColumnInformation( /// - public string ColumnName - { - get { return _columnName; } - } + public string ColumnName => _columnName; /// - public string TrimmedColumnName - { - get { return _trimmedColumnName; } - } + public string TrimmedColumnName => _trimmedColumnName; /// - public bool IsPrimaryKey - { - get { return _isPrimaryKey; } - } + public bool IsPrimaryKey => _isPrimaryKey; /// - public bool IsIdentity - { - get { return _isIdentity; } - } + public bool IsIdentity => _isIdentity; /// - public bool IsComputed - { - get { return _isComputed; } - } + public bool IsComputed => _isComputed; /// - public bool IsNullable - { - get { return _isNullable; } - } + public bool IsNullable => _isNullable; /// - public Type Type - { - get { return _type; } - } + public Type Type => _type; /// - public SqlDbType SqlDbType - { - get { return _sqlDbType; } - } + public SqlDbType SqlDbType => _sqlDbType; /// public override int GetHashCode() { - if (false == _hasCode.HasValue) + if (!_hashCode.HasValue) { - _hasCode = _columnName.GetHashCode() ^ + _hashCode = _columnName.GetHashCode() ^ _trimmedColumnName.GetHashCode() ^ _isPrimaryKey.GetHashCode() ^ _isIdentity.GetHashCode() ^ @@ -135,7 +112,7 @@ public override int GetHashCode() _sqlDbType.GetHashCode(); } - return _hasCode.Value; + return _hashCode.Value; } } } From 5a6d72eaeea1ad16f83c86b9a1768d56ede273f1 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 24 Jun 2016 16:22:39 +0200 Subject: [PATCH 090/213] F --- Composite/Data/DataFacade.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Composite/Data/DataFacade.cs b/Composite/Data/DataFacade.cs index 0c7fa32cb8..8bdf62842f 100644 --- a/Composite/Data/DataFacade.cs +++ b/Composite/Data/DataFacade.cs @@ -600,7 +600,11 @@ public static IData TryGetDataByUniqueKey(Type interfaceType, object dataKeyValu { Verify.ArgumentNotNull(interfaceType, "interfaceType"); - if(DataCachingFacade.IsDataAccessCacheEnabled(interfaceType)) + var properties = interfaceType.GetPhysicalKeyProperties(); + + // TODO: implemented cached lookup for versioned data types + if (DataCachingFacade.IsDataAccessCacheEnabled(interfaceType) + && properties.Count == 1) { var cachedByKey = DataFacade.GetData(interfaceType) as CachingQueryable_CachedByKey; if(cachedByKey != null) @@ -608,12 +612,10 @@ public static IData TryGetDataByUniqueKey(Type interfaceType, object dataKeyValu return cachedByKey.GetCachedValueByKey(dataKeyValue); } } - - var propertyInfos = DataAttributeFacade.GetPhysicalKeyProperties(interfaceType); - DataKeyPropertyCollection dataKeyPropertyCollection = new DataKeyPropertyCollection(); - foreach (var propertyInfo in propertyInfos) + var dataKeyPropertyCollection = new DataKeyPropertyCollection(); + foreach (var propertyInfo in properties) { dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); } From f218545e900a0c1be4fa30673c162055b9c86085 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 29 Jun 2016 12:18:38 +0200 Subject: [PATCH 091/213] Fixing DataPackageFragmentInstaller validation --- .../DataPackageFragmentInstaller.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs index db1371f09f..be2e3f7b90 100644 --- a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs +++ b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs @@ -508,8 +508,11 @@ private void ValidateNonDynamicAddedType(DataType dataType) continue; } + // Backward compatibility + AddDefaultVersionId(dataKeyPropertyCollection, dataType.InterfaceType); - // Validating keys already present + + // Validating keys already present if (!dataType.AllowOverwrite && !dataType.OnlyUpdate) { bool dataLocaleExists = @@ -556,6 +559,16 @@ private void ValidateNonDynamicAddedType(DataType dataType) } } + private void AddDefaultVersionId(DataKeyPropertyCollection keyCollection, Type dataType) + { + if (typeof(IVersioned).IsAssignableFrom(dataType) + && !keyCollection.KeyProperties.Any(k => k.Key == nameof(IVersioned.VersionId))) + { + keyCollection.AddKeyProperty(nameof(IVersioned.VersionId), Guid.Empty); + } + } + + private void CheckForBrokenReference(DataType refereeType, Type type, string propertyName, object propertyValue) { Type referredType; From 02fba783016cdc34bb02fdd34cc5a69004cc3ab6 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 1 Jul 2016 13:34:24 +0200 Subject: [PATCH 092/213] Data cache should cache data instances not processed by data interceptors --- Composite/Data/Caching/DataCachingFacade.cs | 4 +- Composite/Data/DataFacadeImpl.cs | 70 +++++++++++---------- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/Composite/Data/Caching/DataCachingFacade.cs b/Composite/Data/Caching/DataCachingFacade.cs index 9f4b5a5f19..a63a707fec 100644 --- a/Composite/Data/Caching/DataCachingFacade.cs +++ b/Composite/Data/Caching/DataCachingFacade.cs @@ -111,7 +111,7 @@ public static bool IsDataAccessCacheEnabled(Type interfaceType) /// - internal static IQueryable GetDataFromCache() + internal static IQueryable GetDataFromCache(Func> getQueryFunc) where T : class, IData { Verify.That(_isEnabled, "The cache is disabled."); @@ -151,7 +151,7 @@ internal static IQueryable GetDataFromCache() CachedTable cachedTable; if (!localizationScopeData.TryGetValue(localizationScope, out cachedTable)) { - IQueryable wholeTable = DataFacade.GetData(false, null); + IQueryable wholeTable = getQueryFunc(); if(!DataProvidersSupportDataWrapping(typeof(T))) { diff --git a/Composite/Data/DataFacadeImpl.cs b/Composite/Data/DataFacadeImpl.cs index d455f3cbce..81872a601f 100644 --- a/Composite/Data/DataFacadeImpl.cs +++ b/Composite/Data/DataFacadeImpl.cs @@ -22,7 +22,7 @@ namespace Composite.Data { internal class DataFacadeImpl : IDataFacade { - private static readonly string LogTitle = "DataFacade"; + private static readonly string LogTitle = nameof(DataFacade); internal Dictionary GlobalDataInterceptors = new Dictionary(); @@ -47,37 +47,16 @@ public IQueryable GetData(bool useCaching, IEnumerable providerNam if (DataProviderRegistry.AllInterfaces.Contains(typeof (T))) { - if (useCaching && DataCachingFacade.IsDataAccessCacheEnabled(typeof (T))) + if (useCaching + && providerNames == null + && DataCachingFacade.IsDataAccessCacheEnabled(typeof (T))) { - resultQueryable = DataCachingFacade.GetDataFromCache(); + resultQueryable = DataCachingFacade.GetDataFromCache( + () => BuildQueryFromProviders(null)); } else { - if (providerNames == null) - { - providerNames = DataProviderRegistry.GetDataProviderNamesByInterfaceType(typeof (T)); - } - - List> queryables = new List>(); - foreach (string providerName in providerNames) - { - IQueryable queryable = DataProviderPluginFacade.GetData(providerName); - - queryables.Add(queryable); - } - - bool resultIsCached = queryables.Count == 1 && queryables[0] is ICachedQuery; - - if (resultIsCached) - { - resultQueryable = queryables[0]; - } - else - { - var multipleSourceQueryable = new DataFacadeQueryable(queryables); - - resultQueryable = multipleSourceQueryable; - } + resultQueryable = BuildQueryFromProviders(providerNames); } } else @@ -109,6 +88,31 @@ public IQueryable GetData(bool useCaching, IEnumerable providerNam } + private IQueryable BuildQueryFromProviders(IEnumerable providerNames) where T : class, IData + { + if (providerNames == null) + { + providerNames = DataProviderRegistry.GetDataProviderNamesByInterfaceType(typeof(T)); + } + + var queries = new List>(); + foreach (string providerName in providerNames) + { + IQueryable query = DataProviderPluginFacade.GetData(providerName); + + queries.Add(query); + } + + bool resultIsCached = queries.Count == 1 && queries[0] is ICachedQuery; + + if (resultIsCached) + { + return queries[0]; + } + + return new DataFacadeQueryable(queries); + } + public T GetDataFromDataSourceId(DataSourceId dataSourceId, bool useCaching) where T : class, IData @@ -162,18 +166,18 @@ public T GetDataFromDataSourceId(DataSourceId dataSourceId, bool useCaching) public IEnumerable GetDataInterceptors(Type dataType) { - DataInterceptor globaDataInterceptor = GlobalDataInterceptors + DataInterceptor globalDataInterceptor = GlobalDataInterceptors .FirstOrDefault(kvp => kvp.Key.IsAssignableFrom(dataType)).Value; DataInterceptor threadedDataInterceptor; this.DataInterceptors.TryGetValue(dataType, out threadedDataInterceptor); - if (threadedDataInterceptor == null && globaDataInterceptor == null) + if (threadedDataInterceptor == null && globalDataInterceptor == null) { return Enumerable.Empty(); } - var dataInterceptors = new List { threadedDataInterceptor, globaDataInterceptor }; + var dataInterceptors = new List { threadedDataInterceptor, globalDataInterceptor }; return dataInterceptors.Where(d => d != null); } @@ -242,7 +246,7 @@ private Dictionary DataInterceptors var threadData = ThreadDataManager.GetCurrentNotNull(); - Dictionary dataInterceptors = threadData.GetValue(threadDataKey) as Dictionary; + var dataInterceptors = threadData.GetValue(threadDataKey) as Dictionary; if (dataInterceptors == null) { @@ -260,7 +264,7 @@ public void Update(IEnumerable dataset, bool suppressEventing, bool perfo { Verify.ArgumentNotNull(dataset, "dataset"); - Dictionary>> sortedDataset = dataset.ToDataProviderAndInterfaceTypeSortedDictionary(); + var sortedDataset = dataset.ToDataProviderAndInterfaceTypeSortedDictionary(); if (!suppressEventing) { From 4a74302873a0973c4fcbf56eb678ab7ffa148b38 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Fri, 1 Jul 2016 15:40:22 +0200 Subject: [PATCH 093/213] Rolling back to not supporting versions in package data --- .../DataPackageFragmentInstaller.cs | 15 +----------- Composite/Data/DataFacade.cs | 23 ++++++------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs index be2e3f7b90..db1371f09f 100644 --- a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs +++ b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs @@ -508,11 +508,8 @@ private void ValidateNonDynamicAddedType(DataType dataType) continue; } - // Backward compatibility - AddDefaultVersionId(dataKeyPropertyCollection, dataType.InterfaceType); - - // Validating keys already present + // Validating keys already present if (!dataType.AllowOverwrite && !dataType.OnlyUpdate) { bool dataLocaleExists = @@ -559,16 +556,6 @@ private void ValidateNonDynamicAddedType(DataType dataType) } } - private void AddDefaultVersionId(DataKeyPropertyCollection keyCollection, Type dataType) - { - if (typeof(IVersioned).IsAssignableFrom(dataType) - && !keyCollection.KeyProperties.Any(k => k.Key == nameof(IVersioned.VersionId))) - { - keyCollection.AddKeyProperty(nameof(IVersioned.VersionId), Guid.Empty); - } - } - - private void CheckForBrokenReference(DataType refereeType, Type type, string propertyName, object propertyValue) { Type referredType; diff --git a/Composite/Data/DataFacade.cs b/Composite/Data/DataFacade.cs index 8bdf62842f..fad3633f4c 100644 --- a/Composite/Data/DataFacade.cs +++ b/Composite/Data/DataFacade.cs @@ -489,7 +489,7 @@ public static LambdaExpression GetPredicateExpressionByUniqueKey(Type interfaceT if (interfaceType == null) throw new ArgumentNullException("interfaceType"); if (dataKeyPropertyCollection == null) throw new ArgumentNullException("dataKeyPropertyCollection"); - var keyProperties = DataAttributeFacade.GetPhysicalKeyProperties(interfaceType); + var keyProperties = DataAttributeFacade.GetKeyProperties(interfaceType); ParameterExpression parameterExpression = Expression.Parameter(interfaceType, "data"); @@ -510,13 +510,10 @@ public static LambdaExpression GetPredicateExpressionByUniqueKey(Type interfaceT { if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - var propertyInfos = DataAttributeFacade.GetPhysicalKeyProperties(interfaceType); + PropertyInfo propertyInfo = DataAttributeFacade.GetKeyProperties(interfaceType).Single(); + var dataKeyPropertyCollection = new DataKeyPropertyCollection(); - foreach (var propertyInfo in propertyInfos) - { dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); - } - return GetPredicateExpressionByUniqueKey(interfaceType, dataKeyPropertyCollection); } @@ -600,11 +597,7 @@ public static IData TryGetDataByUniqueKey(Type interfaceType, object dataKeyValu { Verify.ArgumentNotNull(interfaceType, "interfaceType"); - var properties = interfaceType.GetPhysicalKeyProperties(); - - // TODO: implemented cached lookup for versioned data types - if (DataCachingFacade.IsDataAccessCacheEnabled(interfaceType) - && properties.Count == 1) + if(DataCachingFacade.IsDataAccessCacheEnabled(interfaceType)) { var cachedByKey = DataFacade.GetData(interfaceType) as CachingQueryable_CachedByKey; if(cachedByKey != null) @@ -612,14 +605,12 @@ public static IData TryGetDataByUniqueKey(Type interfaceType, object dataKeyValu return cachedByKey.GetCachedValueByKey(dataKeyValue); } } + + PropertyInfo propertyInfo = DataAttributeFacade.GetKeyProperties(interfaceType).Single(); - var dataKeyPropertyCollection = new DataKeyPropertyCollection(); - foreach (var propertyInfo in properties) - { + DataKeyPropertyCollection dataKeyPropertyCollection = new DataKeyPropertyCollection(); dataKeyPropertyCollection.AddKeyProperty(propertyInfo, dataKeyValue); - } - return TryGetDataByUniqueKey(interfaceType, dataKeyPropertyCollection); } From fe7353583351a0249a632d77d9369e85b4650a62 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 4 Jul 2016 14:27:19 +0200 Subject: [PATCH 094/213] Fixing TryGetDataByUniqueKey for sql data provider --- Composite/Data/Caching/CachingQueryable.cs | 101 ++++++++++++-------- Composite/Data/Caching/DataCachingFacade.cs | 16 +--- 2 files changed, 64 insertions(+), 53 deletions(-) diff --git a/Composite/Data/Caching/CachingQueryable.cs b/Composite/Data/Caching/CachingQueryable.cs index 4d24a6394e..3247daa711 100644 --- a/Composite/Data/Caching/CachingQueryable.cs +++ b/Composite/Data/Caching/CachingQueryable.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Linq.Expressions; using System.Threading; -using Composite.Core.Collections.Generic; using Composite.Core.Types; using Composite.Data.Caching.Foundation; using System.Reflection; @@ -57,7 +56,7 @@ internal sealed class CachingQueryable : ICachedQuery, IOrderedQueryable, private readonly CachingEnumerable _wrappedEnumerable; private readonly Func _getQueryFunc; - private readonly DataCachingFacade.CachedTable _cachedTable; + private volatile DataCachingFacade.CachedTable _cachedTable; private static readonly MethodInfo _wrappingMethodInfo; private IEnumerable _innerEnumerable; @@ -202,66 +201,84 @@ public object Execute(Expression expression) public IData GetCachedValueByKey(object key) { - if (_cachedTable.RowByKey == null) - { - lock(_cachedTable) - { - if (_cachedTable.RowByKey == null) - { - var table = new Hashtable(); + IEnumerable cachedRows = GetRowsByKeyTable()[key]; + if (cachedRows == null) return null; - PropertyInfo keyPropertyInfo = DataAttributeFacade.GetKeyProperties(typeof(T)).Single(); + IEnumerable filteredData = cachedRows.Cast(); - IEnumerable enumerable = BuildEnumerable(); + var filterMethodInfo = StaticReflection.GetGenericMethodInfo( + () => ((DataInterceptor)null).InterceptGetData((IEnumerable)null)) + .MakeGenericMethod(typeof(T)); - var emptyIndexCollection = new object[0]; + foreach (var dataInterceptor in DataFacade.GetDataInterceptors(typeof(T))) + { + filteredData = (IEnumerable)filterMethodInfo.Invoke(dataInterceptor, new object[] { filteredData }); + } - foreach(T row in enumerable) - { - object rowKey = keyPropertyInfo.GetValue(row, emptyIndexCollection); + var result = filteredData.FirstOrDefault(); + if (result == null) + { + return null; + } - table.Add(rowKey, row); - } + return _wrappingMethodInfo.Invoke(null, new object[] { result }) as IData; + } - _cachedTable.RowByKey = table; - } - } + + Dictionary> GetRowsByKeyTable() + { + var cachedTable = GetCachedTable(); + + var result = cachedTable.RowsByKey; + if (result != null) + { + return result; } - object cachedRow = _cachedTable.RowByKey[key]; - if (cachedRow == null) return null; + lock (cachedTable) + { + result = cachedTable.RowsByKey; + if (result != null) + { + return result; + } - return _wrappingMethodInfo.Invoke(null, new object[] { cachedRow }) as IData; - } + PropertyInfo keyPropertyInfo = typeof(T).GetKeyProperties().Single(); + result = BuildEnumerable() + .GroupBy(data => keyPropertyInfo.GetValue(data, null)) + .ToDictionary(group => group.Key, group => group.ToArray() as IEnumerable); - public Expression Expression - { - get { return _currentExpression; } + return cachedTable.RowsByKey = result; + } } - - public Type ElementType + private DataCachingFacade.CachedTable GetCachedTable() { - get { return typeof(T); } + if (_cachedTable == null) + { + lock (this) + { + if (_cachedTable == null) + { + _cachedTable = new DataCachingFacade.CachedTable(GetOriginalQuery()); + } + } + } + + return _cachedTable; } + public Expression Expression => _currentExpression; - public IQueryProvider Provider - { - get { return this; } - } + public Type ElementType => typeof(T); - public IQueryable Source - { - get { return _source; } - } + public IQueryProvider Provider => this; - public IQueryable GetOriginalQuery() - { - return _getQueryFunc(); - } + public IQueryable Source => _source; + + public IQueryable GetOriginalQuery() => _getQueryFunc(); } } diff --git a/Composite/Data/Caching/DataCachingFacade.cs b/Composite/Data/Caching/DataCachingFacade.cs index a63a707fec..08440397a3 100644 --- a/Composite/Data/Caching/DataCachingFacade.cs +++ b/Composite/Data/Caching/DataCachingFacade.cs @@ -65,21 +65,15 @@ public CachedTable(IQueryable queryable) /// /// Row by key table /// - public Hashtable RowByKey; + public Dictionary> RowsByKey; } /// /// Gets a value indicating if data caching is enabled /// - public static bool Enabled - { - get - { - return _isEnabled; - } - } + public static bool Enabled => _isEnabled; + - /// /// Gets a value indicating if data caching is possible for a specific data type /// @@ -194,7 +188,7 @@ internal static IQueryable GetDataFromCache(Func> getQueryFu { using (new DataScope(dataScopeIdentifier, localizationScope)) { - return DataFacade.GetData(false); + return getQueryFunc(); } }; @@ -303,7 +297,7 @@ private static MethodInfo GetQueryableTakeMethodInfo(Type type) if(_queryableTakeMathodInfo == null) { _queryableTakeMathodInfo = (from method in typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public) - where method.Name == "Take" && + where method.Name == nameof(Queryable.Take) && method.IsGenericMethod select method).First(); } From 6696aae04df7de239fb7eff49ecd45cc1b0c26a9 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 4 Jul 2016 14:28:54 +0200 Subject: [PATCH 095/213] Fixing a typo --- Composite/C1Console/Forms/FormTreeCompiler.cs | 2 +- .../FormFlowRendering/FormFlowUiDefinitionRenderer.cs | 6 +++--- .../UiContainerFactories/TemplatedUiContainerBase.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Composite/C1Console/Forms/FormTreeCompiler.cs b/Composite/C1Console/Forms/FormTreeCompiler.cs index ea0d5cabab..162e07ad12 100644 --- a/Composite/C1Console/Forms/FormTreeCompiler.cs +++ b/Composite/C1Console/Forms/FormTreeCompiler.cs @@ -138,7 +138,7 @@ public Dictionary SaveAndValidateControlProperties() public Dictionary GetBindingToClientIDMapping() { var result = new Dictionary(); - FormFlowUiDefinitionRenderer.ResolveBindingPathToCliendIDMappings(_uiControl as IWebUiControl, result); + FormFlowUiDefinitionRenderer.ResolveBindingPathToClientIDMappings(_uiControl as IWebUiControl, result); return result; } diff --git a/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs b/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs index 16941ab8e6..9aab4179ef 100644 --- a/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs +++ b/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs @@ -308,7 +308,7 @@ private static IWebUiContainer CurrentControlContainer private static void ShowFieldMessages(IWebUiControl webUiControlTreeRoot, Dictionary bindingPathedMessages, IWebUiContainer container, FlowControllerServicesContainer servicesContainer) { var pathToClientIDMappings = new Dictionary(); - ResolveBindingPathToCliendIDMappings(webUiControlTreeRoot, pathToClientIDMappings); + ResolveBindingPathToClientIDMappings(webUiControlTreeRoot, pathToClientIDMappings); var cliendIDPathedMessages = new Dictionary(); var homelessMessages = new Dictionary(); @@ -344,14 +344,14 @@ private static void ShowFieldMessages(IWebUiControl webUiControlTreeRoot, Dictio } - internal static void ResolveBindingPathToCliendIDMappings(IWebUiControl webUiControl, Dictionary resolvedMappings) + internal static void ResolveBindingPathToClientIDMappings(IWebUiControl webUiControl, Dictionary resolvedMappings) { var container = webUiControl as ContainerUiControlBase; if (container != null) { foreach (IUiControl child in container.UiControls) { - ResolveBindingPathToCliendIDMappings((IWebUiControl)child, resolvedMappings); + ResolveBindingPathToClientIDMappings((IWebUiControl)child, resolvedMappings); } } diff --git a/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs b/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs index 62aca196b5..a219aeb31a 100644 --- a/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs +++ b/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs @@ -44,7 +44,7 @@ public string GetTitleFieldControlId() var mappings = new Dictionary(); - FormFlowUiDefinitionRenderer.ResolveBindingPathToCliendIDMappings(container, mappings); + FormFlowUiDefinitionRenderer.ResolveBindingPathToClientIDMappings(container, mappings); string clientId = mappings.ContainsKey(_titleField) ? mappings[_titleField] : ""; From 2a4506596821da3a29ec55724fb99d6c0a11a2b1 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 6 Jul 2016 14:37:46 +0200 Subject: [PATCH 096/213] BoolSelector - properly loading post data, fixing radio buttons' labels encoding, adding SelectionChangedEventHandler property --- .../CoreUiControls/BoolSelectorUiControl.cs | 12 ++-- .../WebClient/UiControlLib/ToolbarButton.cs | 6 +- .../TemplatedBoolSelectorUiControlFactory.cs | 49 ++++++++----- .../BoolSelectors/BoolSelector.ascx | 72 +++++++++++-------- 4 files changed, 85 insertions(+), 54 deletions(-) diff --git a/Composite/C1Console/Forms/CoreUiControls/BoolSelectorUiControl.cs b/Composite/C1Console/Forms/CoreUiControls/BoolSelectorUiControl.cs index c5adf85688..c3d8f88589 100644 --- a/Composite/C1Console/Forms/CoreUiControls/BoolSelectorUiControl.cs +++ b/Composite/C1Console/Forms/CoreUiControls/BoolSelectorUiControl.cs @@ -8,16 +8,20 @@ namespace Composite.C1Console.Forms.CoreUiControls [ControlValueProperty("IsTrue")] internal abstract class BoolSelectorUiControl : UiControl { - [BindableProperty()] - [FormsProperty()] + [BindableProperty] + [FormsProperty] public bool IsTrue { get; set; } - [FormsProperty()] + [FormsProperty] public string TrueLabel { get; set; } - [FormsProperty()] + [FormsProperty] public string FalseLabel { get; set; } + + [BindableProperty] + [FormsProperty] + public EventHandler SelectionChangedEventHandler { get; set; } } } diff --git a/Composite/Core/WebClient/UiControlLib/ToolbarButton.cs b/Composite/Core/WebClient/UiControlLib/ToolbarButton.cs index 1650aff210..f027ea9ea9 100644 --- a/Composite/Core/WebClient/UiControlLib/ToolbarButton.cs +++ b/Composite/Core/WebClient/UiControlLib/ToolbarButton.cs @@ -12,15 +12,15 @@ namespace Composite.Core.WebClient.UiControlLib public class ToolbarButton : LinkButton { /// - [Category("Behavior"), DefaultValue(""), Description("The id as the ui client should see")] + [Category("Behavior"), DefaultValue(""), Description("The id as the UI client should see")] public virtual string CustomClientId { get; set; } /// - [Category("Appearance"), DefaultValue(""), Description("Image to show in the buttom")] + [Category("Appearance"), DefaultValue(""), Description("Image to show on the button")] public virtual string ImageUrl { get; set; } /// - [Category("Appearance"), DefaultValue(""), Description("Image to show in the buttom when the button is disabled")] + [Category("Appearance"), DefaultValue(""), Description("Image to show on the button when it is disabled")] public virtual string ImageUrlWhenDisabled { get; set; } /// diff --git a/Composite/Plugins/Forms/WebChannel/UiControlFactories/TemplatedBoolSelectorUiControlFactory.cs b/Composite/Plugins/Forms/WebChannel/UiControlFactories/TemplatedBoolSelectorUiControlFactory.cs index 26375da70c..b911815e4b 100644 --- a/Composite/Plugins/Forms/WebChannel/UiControlFactories/TemplatedBoolSelectorUiControlFactory.cs +++ b/Composite/Plugins/Forms/WebChannel/UiControlFactories/TemplatedBoolSelectorUiControlFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Specialized; using System.Configuration; using System.Web.UI; using Composite.C1Console.Forms; @@ -17,11 +18,8 @@ namespace Composite.Plugins.Forms.WebChannel.UiControlFactories ///
/// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public abstract class BoolSelectorTemplateUserControlBase : UserControl + public abstract class BoolSelectorTemplateUserControlBase : UserControl, IPostBackDataHandler { - private string _formControlLabel; - private bool _isTrue; - /// protected abstract void BindStateToProperties(); @@ -42,30 +40,42 @@ internal void InitializeWebViewState() } /// - public bool IsTrue - { - get { return _isTrue; } - set { _isTrue = value; } - } + public bool IsTrue { get; set; } /// - public string FormControlLabel - { - get { return _formControlLabel; } - set { _formControlLabel = value; } - } - + public string FormControlLabel { get; set; } /// public string TrueLabel { get; set; } /// public string FalseLabel { get; set; } + + /// + public EventHandler SelectionChangedEventHandler { get; set; } + + /// + /// When implemented by a class, processes postback data for an ASP.NET server control. + /// + /// + /// + /// + public virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) + { + return false; + } + + /// + /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. + /// + public virtual void RaisePostDataChangedEvent() + { + } } internal sealed class TemplatedBoolSelectorUiControl : BoolSelectorUiControl, IWebUiControl { - private Type _userControlType; + private readonly Type _userControlType; private BoolSelectorTemplateUserControlBase _userControl; internal TemplatedBoolSelectorUiControl(Type userControlType) @@ -93,13 +103,14 @@ public Control BuildWebControl() _userControl.IsTrue = this.IsTrue; _userControl.TrueLabel = this.TrueLabel; _userControl.FalseLabel = this.FalseLabel; + _userControl.SelectionChangedEventHandler += this.SelectionChangedEventHandler; return _userControl; } - public bool IsFullWidthControl { get { return false; } } + public bool IsFullWidthControl => false; - public string ClientName { get { return _userControl.GetDataFieldClientName(); } } + public string ClientName => _userControl.GetDataFieldClientName(); } @@ -112,7 +123,7 @@ public TemplatedBoolSelectorUiControlFactory(TemplatedBoolSelectorUiControlFacto public override IUiControl CreateControl() { - TemplatedBoolSelectorUiControl control = new TemplatedBoolSelectorUiControl(this.UserControlType); + var control = new TemplatedBoolSelectorUiControl(this.UserControlType); return control; } diff --git a/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx b/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx index 6e0e365350..13b559fafb 100644 --- a/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx +++ b/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx @@ -1,34 +1,50 @@ <%@ Control Language="C#" Inherits="Composite.Plugins.Forms.WebChannel.UiControlFactories.BoolSelectorTemplateUserControlBase" %> -<%@ Import Namespace="Composite.Plugins.Forms.WebChannel.UiControlFactories" %> - - - + + + From bcaff2cbf60255a1b8583fc81dd16b1b27ae4bf9 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Wed, 6 Jul 2016 16:55:36 -0400 Subject: [PATCH 097/213] add postback for BoolSelector --- .../BoolSelectors/BoolSelector.ascx | 2 +- .../data/radiocheck/RadioDataGroupBinding.js | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx b/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx index 13b559fafb..513cd3df67 100644 --- a/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx +++ b/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx @@ -44,7 +44,7 @@ } - + diff --git a/Website/Composite/scripts/source/top/ui/bindings/data/radiocheck/RadioDataGroupBinding.js b/Website/Composite/scripts/source/top/ui/bindings/data/radiocheck/RadioDataGroupBinding.js index f2990942a1..cdb766d5cc 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/data/radiocheck/RadioDataGroupBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/data/radiocheck/RadioDataGroupBinding.js @@ -77,6 +77,13 @@ RadioDataGroupBinding.prototype.onBindingAttach = function () { self.focus ( true ); } }); + + var onchange = this.getProperty("onchange"); + if (onchange) { + this.onValueChange = function () { + Binding.evaluate(onchange, this); + }; + } } /** @@ -100,7 +107,8 @@ RadioDataGroupBinding.prototype.handleAction = function ( action ) { switch ( action.type ) { case RadioGroupBinding.ACTION_SELECTIONCHANGED : - this.dirty (); + this.dirty(); + this.onValueChange(); break; } } @@ -308,4 +316,6 @@ RadioDataGroupBinding.prototype.setValue = function ( value ) {} * @implements {IData} * @param {object} result */ -RadioDataGroupBinding.prototype.setResult = function ( result ) {} \ No newline at end of file +RadioDataGroupBinding.prototype.setResult = function (result) { } + +RadioDataGroupBinding.prototype.onValueChange = function () { } \ No newline at end of file From 774916cd9226207b81e8f445164d98cd53bb7f61 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 7 Jul 2016 10:22:27 +0200 Subject: [PATCH 098/213] BoolSelector - making a postback on change only if there's a selection change handler --- .../BoolSelectors/BoolSelector.ascx | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx b/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx index 513cd3df67..30a78252c4 100644 --- a/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx +++ b/Website/Composite/controls/FormsControls/FormUiControlTemplates/BoolSelectors/BoolSelector.ascx @@ -1,10 +1,13 @@ -<%@ Control Language="C#" Inherits="Composite.Plugins.Forms.WebChannel.UiControlFactories.BoolSelectorTemplateUserControlBase" %> +<%@ Control Language="C#" EnableViewState="true" Inherits="Composite.Plugins.Forms.WebChannel.UiControlFactories.BoolSelectorTemplateUserControlBase" %> - + > From 9434fca0bd4e1f5de41eea9a7684f0fe76a9ae0a Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Fri, 8 Jul 2016 16:55:55 -0400 Subject: [PATCH 099/213] styles --- .../GenericPublishProcessController.cs | 3 +- .../views/browser/BrowserPageBinding.js | 7 ++- .../content/views/browser/browser.css | 4 ++ .../default/toolbars/toolbars-base.less | 47 +++++++++---------- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs b/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs index 8d763df314..ef02e97d96 100644 --- a/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs +++ b/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs @@ -183,7 +183,8 @@ public GenericPublishProcessController() ActionType = ActionType.Other, IsInFolder = false, IsInToolbar = true, - ActionGroup = WorkflowActionGroup + ActionGroup = WorkflowActionGroup, + ActionBundle = "Publish" } } }; diff --git a/Website/Composite/content/views/browser/BrowserPageBinding.js b/Website/Composite/content/views/browser/BrowserPageBinding.js index 2b6d9be700..a9c0b98e85 100644 --- a/Website/Composite/content/views/browser/BrowserPageBinding.js +++ b/Website/Composite/content/views/browser/BrowserPageBinding.js @@ -14,7 +14,9 @@ BrowserPageBinding.VIEWMODE_LOCALSTORAGE_KEY = "COMPOSITE_BROWSERPAGEBINDING_VIE BrowserPageBinding.VIEW_MODES = Object.freeze({ Unpublic: 1, Public: 2 -}) +}); + +BrowserPageBinding.BUNDLE_CLASSNAME = "bundleselector"; /** @@ -146,7 +148,7 @@ BrowserPageBinding.prototype.handleBroadcast = function (broadcast, arg) { if (treenode.node.isMultiple()) { var list = new List(); treenode.node.getDatas().each(function(data) { - list.add(new SelectorBindingSelection(data.BundleElementName, data.ElementKey, data.ElementKey === treenode.node.getHandle())); + list.add(new SelectorBindingSelection(data.BundleElementName ? data.BundleElementName : data.Label, data.ElementKey, data.ElementKey === treenode.node.getHandle())); }); bundleselector.populateFromList(list); bundleselector.show(); @@ -1240,6 +1242,7 @@ BrowserPageBinding.prototype.getBundleSelector = function () { selector.setProperty("textonly", true); addressrightgroup.bindingElement.insertBefore(selector.bindingElement, addressrightgroup.bindingElement.firstChild); selector.attach(); + selector.attachClassName(BrowserPageBinding.BUNDLE_CLASSNAME); this.shadowTree.bundleselector = selector; selector.addActionListener(SelectorBinding.ACTION_SELECTIONCHANGED, { diff --git a/Website/Composite/content/views/browser/browser.css b/Website/Composite/content/views/browser/browser.css index 4da389c216..fcd047cdeb 100644 --- a/Website/Composite/content/views/browser/browser.css +++ b/Website/Composite/content/views/browser/browser.css @@ -88,4 +88,8 @@ ui|toolbar#breadcrumbbar { .selectorpopup ui|labelbox.image-and-text ui|labeltext { margin-left: 4px; +} + +ui|selector.bundleselector { + min-width: 200px; } \ No newline at end of file diff --git a/Website/Composite/styles/default/toolbars/toolbars-base.less b/Website/Composite/styles/default/toolbars/toolbars-base.less index 914f1ec50d..ff8cfe9504 100644 --- a/Website/Composite/styles/default/toolbars/toolbars-base.less +++ b/Website/Composite/styles/default/toolbars/toolbars-base.less @@ -66,35 +66,34 @@ ui|toolbargroup { } } -ui|toolbar.btns-group { - ui|toolbargroup { - border: 1px solid @base-border-color; - border-radius: 3px; - margin: 4px 5px; - padding: 0; - overflow: hidden; +ui|toolbar.btns-group ui|toolbargroup, +ui|toolbargroup.btns-group { + border: 1px solid @base-border-color; + border-radius: 3px; + margin: 4px 5px; + padding: 0; + overflow: hidden; - &:empty { - display: none; - } + &:empty { + display: none; + } - ui|toolbarbutton { - border-color: transparent; - margin: 0; - border-radius: 0; - text-transform: none; + ui|toolbarbutton { + border-color: transparent; + margin: 0; + border-radius: 0; + text-transform: none; - + ui|toolbarbutton { - border-color: transparent transparent transparent @base-border-color; - } + + ui|toolbarbutton { + border-color: transparent transparent transparent @base-border-color; + } - ui|labelbox { - padding: 5px 9px; - } + ui|labelbox { + padding: 5px 9px; + } - ui|labelbody { - min-width: 18px; - } + ui|labelbody { + min-width: 18px; } } } From 2abfcec154ce92f9e0e6aacacbc2cdffe8963f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 10 Jun 2016 17:03:43 +0200 Subject: [PATCH 100/213] Added Nightwatch, simplistic test. --- .gitignore | 4 +- Website/package.json | 37 +++++++------ Website/test/minimal/min.e2e.js | 13 +++++ nightwatch.json | 54 +++++++++++++++++++ reports/CHROME_51.0.2704.84_XP_min.e2e.xml | 21 ++++++++ reports/FIREFOX_46.0_WINDOWS_min.e2e.xml | 21 ++++++++ .../INTERNET EXPLORER_11_WINDOWS_min.e2e.xml | 17 ++++++ 7 files changed, 147 insertions(+), 20 deletions(-) create mode 100644 Website/test/minimal/min.e2e.js create mode 100644 nightwatch.json create mode 100644 reports/CHROME_51.0.2704.84_XP_min.e2e.xml create mode 100644 reports/FIREFOX_46.0_WINDOWS_min.e2e.xml create mode 100644 reports/INTERNET EXPLORER_11_WINDOWS_min.e2e.xml diff --git a/.gitignore b/.gitignore index c4bc566998..b03a1bf2dd 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,6 @@ /Bin/Composite.dll /Bin/Composite.Workflows.dll -/Packages/ \ No newline at end of file +/Packages/ + +selenium-debug.log diff --git a/Website/package.json b/Website/package.json index 9a203c6568..315237a672 100644 --- a/Website/package.json +++ b/Website/package.json @@ -1,19 +1,18 @@ -{ - "name": "CompositeC1", - - "version": "1.0.0", - - "devDependencies": { - "grunt": "~0.4.5", - "grunt-contrib-less": "^1.0.0", - "grunt-contrib-watch": "~0.6.1", - "grunt-contrib-uglify": "^0.9.1", - "grunt-contrib-copy": "0.8.2", - "grunt-postcss": "^0.5.5", - "autoprefixer-core": "^5.2.1", - "csswring": "^3.0.5", - "less": "^2.1.2", - "JSONPath": "^0.10.0", - "xml2js": "^0.4.10" - } -} +{ + "name": "CompositeC1", + "version": "1.0.0", + "devDependencies": { + "JSONPath": "^0.10.0", + "autoprefixer-core": "^5.2.1", + "csswring": "^3.0.5", + "grunt": "~0.4.5", + "grunt-contrib-copy": "0.8.2", + "grunt-contrib-less": "^1.0.0", + "grunt-contrib-uglify": "^0.9.1", + "grunt-contrib-watch": "~0.6.1", + "grunt-postcss": "^0.5.5", + "less": "^2.1.2", + "nightwatch": "0.9.3", + "xml2js": "^0.4.10" + } +} diff --git a/Website/test/minimal/min.e2e.js b/Website/test/minimal/min.e2e.js new file mode 100644 index 0000000000..08151cf4e3 --- /dev/null +++ b/Website/test/minimal/min.e2e.js @@ -0,0 +1,13 @@ +module.exports = { + 'Demo test Google' : function (browser) { + browser + .url('http://www.google.com') + .waitForElementVisible('body', 1000) + .setValue('input[type=text]', 'nightwatch') + .waitForElementVisible('button[name=btnG]', 1000) + .click('button[name=btnG]') + .pause(1000) + .assert.containsText('#main', 'Night Watch') + .end(); + } +} diff --git a/nightwatch.json b/nightwatch.json new file mode 100644 index 0000000000..53349afbfe --- /dev/null +++ b/nightwatch.json @@ -0,0 +1,54 @@ +{ + "src_folders" : ["Website/test/minimal"], + "output_folder" : "reports", + "custom_commands_path" : "", + "custom_assertions_path" : "", + "page_objects_path" : "", + "globals_path" : "", + + "selenium" : { + "start_process" : true, + "server_path" : "Website/test/selenium-server-standalone-2.53.0.jar", + "log_path" : "", + "host" : "127.0.0.1", + "port" : 4444, + "cli_args" : { + "webdriver.chrome.driver" : "Website/test/chromedriver.exe", + "webdriver.ie.driver" : "Website/test/IEDriverServer.exe" + } + }, + + "test_settings" : { + "default" : { + "launch_url" : "http://localhost", + "selenium_port" : 4444, + "selenium_host" : "localhost", + "silent": true, + "screenshots" : { + "enabled" : false, + "path" : "" + }, + "desiredCapabilities": { + "browserName": "firefox", + "javascriptEnabled": true, + "acceptSslCerts": true + } + }, + + "chrome" : { + "desiredCapabilities": { + "browserName": "chrome", + "javascriptEnabled": true, + "acceptSslCerts": true + } + }, + + "ie" : { + "desiredCapabilities": { + "browserName": "internet explorer", + "javascriptEnabled": true, + "acceptSslCerts": true + } + } + } +} diff --git a/reports/CHROME_51.0.2704.84_XP_min.e2e.xml b/reports/CHROME_51.0.2704.84_XP_min.e2e.xml new file mode 100644 index 0000000000..636b4d2e97 --- /dev/null +++ b/reports/CHROME_51.0.2704.84_XP_min.e2e.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/reports/FIREFOX_46.0_WINDOWS_min.e2e.xml b/reports/FIREFOX_46.0_WINDOWS_min.e2e.xml new file mode 100644 index 0000000000..f9a8e48def --- /dev/null +++ b/reports/FIREFOX_46.0_WINDOWS_min.e2e.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/reports/INTERNET EXPLORER_11_WINDOWS_min.e2e.xml b/reports/INTERNET EXPLORER_11_WINDOWS_min.e2e.xml new file mode 100644 index 0000000000..e0c8d81321 --- /dev/null +++ b/reports/INTERNET EXPLORER_11_WINDOWS_min.e2e.xml @@ -0,0 +1,17 @@ + + + + + + at Object.module.exports.Demo test Google (C:\Users\gert.sonderby\Documents\Source\C1-CMS\Website\test\minimal\min.e2e.js:5:8) + + + + + + + From d478769756444d7816b7c750ae728014a478a875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Mon, 13 Jun 2016 16:55:14 +0200 Subject: [PATCH 101/213] Cleaned up repos, polished settings. --- .gitignore | 1 + nightwatch.json | 8 +++++++ reports/CHROME_51.0.2704.84_XP_min.e2e.xml | 21 ------------------- reports/FIREFOX_46.0_WINDOWS_min.e2e.xml | 21 ------------------- .../INTERNET EXPLORER_11_WINDOWS_min.e2e.xml | 17 --------------- 5 files changed, 9 insertions(+), 59 deletions(-) delete mode 100644 reports/CHROME_51.0.2704.84_XP_min.e2e.xml delete mode 100644 reports/FIREFOX_46.0_WINDOWS_min.e2e.xml delete mode 100644 reports/INTERNET EXPLORER_11_WINDOWS_min.e2e.xml diff --git a/.gitignore b/.gitignore index b03a1bf2dd..f668909242 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ /Packages/ selenium-debug.log +reports/ diff --git a/nightwatch.json b/nightwatch.json index 53349afbfe..8f9e340177 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -49,6 +49,14 @@ "javascriptEnabled": true, "acceptSslCerts": true } + }, + + "edge" : { + "desiredCapabilities": { + "browserName": "edge", + "javascriptEnabled": true, + "acceptSslCerts": true + } } } } diff --git a/reports/CHROME_51.0.2704.84_XP_min.e2e.xml b/reports/CHROME_51.0.2704.84_XP_min.e2e.xml deleted file mode 100644 index 636b4d2e97..0000000000 --- a/reports/CHROME_51.0.2704.84_XP_min.e2e.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/reports/FIREFOX_46.0_WINDOWS_min.e2e.xml b/reports/FIREFOX_46.0_WINDOWS_min.e2e.xml deleted file mode 100644 index f9a8e48def..0000000000 --- a/reports/FIREFOX_46.0_WINDOWS_min.e2e.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/reports/INTERNET EXPLORER_11_WINDOWS_min.e2e.xml b/reports/INTERNET EXPLORER_11_WINDOWS_min.e2e.xml deleted file mode 100644 index e0c8d81321..0000000000 --- a/reports/INTERNET EXPLORER_11_WINDOWS_min.e2e.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - at Object.module.exports.Demo test Google (C:\Users\gert.sonderby\Documents\Source\C1-CMS\Website\test\minimal\min.e2e.js:5:8) - - - - - - - From b3873d4c09e67c40880eb43862ae57f5ec453e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Mon, 13 Jun 2016 16:57:30 +0200 Subject: [PATCH 102/213] Switched to testing in the console, added code to find and work with a button inside iframes. Doesn't quite work yet. --- Website/Composite/login.inc | 4 +- .../scripts/source/top/ui/bindings/Binding.js | 4 +- .../source/top/ui/utilities/BindingFinder.js | 63 ++++++++++--------- Website/test/findButton.js | 42 +++++++++++++ Website/test/minimal/min.e2e.js | 36 ++++++++--- nightwatch.json | 4 ++ 6 files changed, 112 insertions(+), 41 deletions(-) create mode 100644 Website/test/findButton.js diff --git a/Website/Composite/login.inc b/Website/Composite/login.inc index c4bced293d..da37351bf2 100644 --- a/Website/Composite/login.inc +++ b/Website/Composite/login.inc @@ -22,7 +22,7 @@ - + @@ -79,4 +79,4 @@ - \ No newline at end of file + diff --git a/Website/Composite/scripts/source/top/ui/bindings/Binding.js b/Website/Composite/scripts/source/top/ui/bindings/Binding.js index a17bfe71fc..33701fccb9 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/Binding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/Binding.js @@ -1532,9 +1532,9 @@ Binding.prototype.getDescendantBindingByLocalName = function ( nodeName ) { * @param {Class} impl * @return {List} */ -Binding.prototype.getDescendantBindingsByType = function ( impl ) { +Binding.prototype.getDescendantBindingsByType = function ( impl, isTraverse ) { - return BindingFinder.getDescendantBindingsByType ( this, impl ); + return BindingFinder.getDescendantBindingsByType ( this, impl, isTraverse ); } /** diff --git a/Website/Composite/scripts/source/top/ui/utilities/BindingFinder.js b/Website/Composite/scripts/source/top/ui/utilities/BindingFinder.js index ecd331b5c8..26f6724c22 100644 --- a/Website/Composite/scripts/source/top/ui/utilities/BindingFinder.js +++ b/Website/Composite/scripts/source/top/ui/utilities/BindingFinder.js @@ -5,7 +5,7 @@ function _BindingFinder () {} _BindingFinder.prototype = { - + /** * Get descendant bindings by nodename. * @param {Binding} source @@ -14,12 +14,12 @@ _BindingFinder.prototype = { * @return {List} */ getDescendantBindingsByLocalName : function ( source, nodeName, isChildrenOnly ) { - + var result = null; if ( source.isAttached ) { result = new List (); var elements = isChildrenOnly ? - source.getChildElementsByLocalName ( nodeName ) : + source.getChildElementsByLocalName ( nodeName ) : source.getDescendantElementsByLocalName ( nodeName ); elements.each ( function ( element ) { var binding = UserInterface.getBinding ( element ); @@ -28,14 +28,14 @@ _BindingFinder.prototype = { } }); } else { - var ouch = "Could not resolve descendants of unattached binding " + source.toString (); + var ouch = "Could not resolve descendants of unattached binding " + source.toString (); if ( Application.isDeveloperMode ) { throw ouch; } } return result; }, - + /** * Get ancestor binding by type. * @param {Binding} source @@ -44,7 +44,7 @@ _BindingFinder.prototype = { * @return {Binding} */ getAncestorBindingByType : function ( source, impl, isTraverse ) { - + var result = null; if ( Binding.exists ( source )) { var node = source.bindingElement; @@ -70,7 +70,7 @@ _BindingFinder.prototype = { } return result; }, - + /** * Get ancestor binding by nodename. * @param {Binding} source @@ -79,7 +79,7 @@ _BindingFinder.prototype = { * @return {Binding} */ getAncestorBindingByLocalName : function ( source, nodeName, isTraverse ) { - + var result = null; if ( nodeName == "*" ) { var node = source.bindingElement; @@ -89,13 +89,13 @@ _BindingFinder.prototype = { } } } else { - result = UserInterface.getBinding ( + result = UserInterface.getBinding ( DOMUtil.getAncestorByLocalName ( nodeName, source.bindingElement, isTraverse ) ); } return result; }, - + /** * Get child elements by nodename. * @param {Binding} source @@ -103,7 +103,7 @@ _BindingFinder.prototype = { * @return {List} */ getChildElementsByLocalName : function ( source, nodeName ) { - + var result = new List (); var children = new List ( source.bindingElement.childNodes ); children.each ( function ( child ) { @@ -115,7 +115,7 @@ _BindingFinder.prototype = { }); return result; }, - + /** * Get the FIRST child binding of a specified type. * @param {Binding} source @@ -123,9 +123,9 @@ _BindingFinder.prototype = { * @return {Binding} */ getChildBindingByType : function ( source, impl ) { - + var result = null; - source.getChildElementsByLocalName ( "*" ).each ( + source.getChildElementsByLocalName ( "*" ).each ( function ( child ) { var binding = UserInterface.getBinding ( child ); if ( binding != null && binding instanceof impl ) { @@ -138,18 +138,18 @@ _BindingFinder.prototype = { ); return result; }, - + /** * Get the FIRST decendant binding of a specified type. - * TODO: Merge with getChildBindingByType. + * TODO: Merge with getChildBindingByType. * @param {Binding} source * @param {Class} impl * @return {Binding} */ getDescendantBindingByType : function ( source, impl ) { - + var result = null; - source.getDescendantElementsByLocalName ( "*" ).each ( + source.getDescendantElementsByLocalName ( "*" ).each ( function ( child ) { var binding = UserInterface.getBinding ( child ); if ( binding != null && binding instanceof impl ) { @@ -162,28 +162,33 @@ _BindingFinder.prototype = { ); return result; }, - + /** - * Get ALL decendant binding of a specified type. + * Get ALL decendant binding of a specified type. * @param {Binding} source * @param {Class} impl * @return {List} */ - getDescendantBindingsByType : function ( source, impl ) { - + getDescendantBindingsByType : function ( source, impl, isTraverse ) { + var result = new List (); - source.getDescendantElementsByLocalName ( "*" ).each ( + source.getDescendantElementsByLocalName ( "*" ).each ( function ( descendant ) { var binding = UserInterface.getBinding ( descendant ); if ( binding != null && binding instanceof impl ) { result.add ( binding ); + } else if( isTraverse && binding instanceof WindowBinding && binding.getRootBinding()) { + this.getDescendantBindingsByType( binding.getRootBinding(), impl, isTraverse) + .each(function (item){ + result.add(item); + }); } return true; } - ); + , this); return result; }, - + /** * Get next binding by name. * @param {Binding} binding @@ -191,7 +196,7 @@ _BindingFinder.prototype = { * @return {Binding} */ getNextBindingByLocalName : function ( binding, name ) { - + var result = null; var element = binding.bindingElement; while (( element = DOMUtil.getNextElementSibling ( element )) != null && DOMUtil.getLocalName ( element ) != name ) {} @@ -200,7 +205,7 @@ _BindingFinder.prototype = { } return result; }, - + /** * Get previous binding by name. * @param {Binding} binding @@ -208,7 +213,7 @@ _BindingFinder.prototype = { * @return {Binding} */ getPreviousBindingByLocalName : function ( binding, name ) { - + var result = null; var element = binding.bindingElement; while (( element = DOMUtil.getPreviousElementSibling ( element )) != null && DOMUtil.getLocalName ( element ) != name ) {} @@ -223,4 +228,4 @@ _BindingFinder.prototype = { * The instance that does it. * @type {_BindingFinder} */ -var BindingFinder = new _BindingFinder (); \ No newline at end of file +var BindingFinder = new _BindingFinder (); diff --git a/Website/test/findButton.js b/Website/test/findButton.js new file mode 100644 index 0000000000..b931f94444 --- /dev/null +++ b/Website/test/findButton.js @@ -0,0 +1,42 @@ +module.exports = { + click: function(browser, actionHandle) { + browser + .execute(function(actionHandle) { + var buttons = UserInterface + .getBinding(document.body) + .getDescendantBindingsByType(ButtonBinding, true); + var ourButton = null; + buttons.each(function(button) { + if (button.associatedSystemAction && + button.associatedSystemAction.getHandle() == actionHandle) { + ourButton = button; + } + }); + + var parentFrameKeys = []; + if (ourButton) { + var node = ourButton.bindingWindow.frameElement; + while (node) { + parentFrameKeys.push(node.name); + node = DOMUtil.getParentWindow(node).frameElement; + } + return { + key: ourButton.getID(), + windowKeys: parentFrameKeys.reverse + }; + } else { + return null; + } + + }, [actionHandle], function(result) { + if (!result.value) throw new Error("Button not found"); + console.log(result.value); + result.value.windowKeys.forEach(function (key) { + browser.frame(key); + }); + browser + .click('#' + result.value.key); + browser.frame(null); + }); + } +}; diff --git a/Website/test/minimal/min.e2e.js b/Website/test/minimal/min.e2e.js index 08151cf4e3..6ff74bc239 100644 --- a/Website/test/minimal/min.e2e.js +++ b/Website/test/minimal/min.e2e.js @@ -1,13 +1,33 @@ +var clickButton = require('../findButton.js').click; +var tmpActionHandle = "actionTokenType='Composite\\.C1Console\\.Actions\\.Data\\.ProxyDataActionToken,Composite'actionToken='_ActionIdentifier_=\\'_ActionIdentifier_=\\\\\\'Edit\\\\\\'\\'_PermissionTypes_=\\'Edit\\'_DoIgnoreEntityTokenLocking_=\\'False\\''actionTokenHash='-1425725536'"; + + module.exports = { - 'Demo test Google' : function (browser) { + 'Demo test login' : function (browser) { browser - .url('http://www.google.com') - .waitForElementVisible('body', 1000) - .setValue('input[type=text]', 'nightwatch') - .waitForElementVisible('button[name=btnG]', 1000) - .click('button[name=btnG]') + .url('http://localhost:57917/Composite/top.aspx?mode=develop') + .waitForElementVisible('#id_username', 1000) + // .setValue('#id_username > box > input', 'admin') + // .setValue('#id_password > box > input', '123456') + .click('#loginButton') .pause(1000) - .assert.containsText('#main', 'Night Watch') + .assert.visible('#appwindow'); + + browser + .execute(function () { + EventBroadcaster.broadcast ( BroadcastMessages.STOP_COMPOSITE ); + }).pause(1000); + + clickButton(browser, tmpActionHandle); + + browser.pause(5000); + + // browser.getLog('browser', function(logEntriesArray) { + // logEntriesArray.forEach(function(log) { + // console.log('[' + log.level + '] ' + log.timestamp + ' : ' + log.message); + // }) + // }); + browser .end(); - } + } } diff --git a/nightwatch.json b/nightwatch.json index 8f9e340177..914261bfbf 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -30,6 +30,7 @@ }, "desiredCapabilities": { "browserName": "firefox", + "loggingPrefs": { "browser": "ALL" } , "javascriptEnabled": true, "acceptSslCerts": true } @@ -38,6 +39,7 @@ "chrome" : { "desiredCapabilities": { "browserName": "chrome", + "loggingPrefs": { "browser": "ALL" } , "javascriptEnabled": true, "acceptSslCerts": true } @@ -46,6 +48,7 @@ "ie" : { "desiredCapabilities": { "browserName": "internet explorer", + "loggingPrefs": { "browser": "ALL" } , "javascriptEnabled": true, "acceptSslCerts": true } @@ -54,6 +57,7 @@ "edge" : { "desiredCapabilities": { "browserName": "edge", + "loggingPrefs": { "browser": "ALL" } , "javascriptEnabled": true, "acceptSslCerts": true } From 31b840874c8c3d2bd3e3aa29f1371c55296aec37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Tue, 14 Jun 2016 15:38:33 +0200 Subject: [PATCH 103/213] Replaced old actionhandle-based find function with new CSS selector based one. --- Website/test/findButton.js | 42 --------------------------------- Website/test/minimal/min.e2e.js | 12 ++++------ Website/test/selectFrame.js | 36 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 49 deletions(-) delete mode 100644 Website/test/findButton.js create mode 100644 Website/test/selectFrame.js diff --git a/Website/test/findButton.js b/Website/test/findButton.js deleted file mode 100644 index b931f94444..0000000000 --- a/Website/test/findButton.js +++ /dev/null @@ -1,42 +0,0 @@ -module.exports = { - click: function(browser, actionHandle) { - browser - .execute(function(actionHandle) { - var buttons = UserInterface - .getBinding(document.body) - .getDescendantBindingsByType(ButtonBinding, true); - var ourButton = null; - buttons.each(function(button) { - if (button.associatedSystemAction && - button.associatedSystemAction.getHandle() == actionHandle) { - ourButton = button; - } - }); - - var parentFrameKeys = []; - if (ourButton) { - var node = ourButton.bindingWindow.frameElement; - while (node) { - parentFrameKeys.push(node.name); - node = DOMUtil.getParentWindow(node).frameElement; - } - return { - key: ourButton.getID(), - windowKeys: parentFrameKeys.reverse - }; - } else { - return null; - } - - }, [actionHandle], function(result) { - if (!result.value) throw new Error("Button not found"); - console.log(result.value); - result.value.windowKeys.forEach(function (key) { - browser.frame(key); - }); - browser - .click('#' + result.value.key); - browser.frame(null); - }); - } -}; diff --git a/Website/test/minimal/min.e2e.js b/Website/test/minimal/min.e2e.js index 6ff74bc239..b4cd3b603b 100644 --- a/Website/test/minimal/min.e2e.js +++ b/Website/test/minimal/min.e2e.js @@ -1,6 +1,4 @@ -var clickButton = require('../findButton.js').click; -var tmpActionHandle = "actionTokenType='Composite\\.C1Console\\.Actions\\.Data\\.ProxyDataActionToken,Composite'actionToken='_ActionIdentifier_=\\'_ActionIdentifier_=\\\\\\'Edit\\\\\\'\\'_PermissionTypes_=\\'Edit\\'_DoIgnoreEntityTokenLocking_=\\'False\\''actionTokenHash='-1425725536'"; - +var selectFrame = require('../selectFrame.js'); module.exports = { 'Demo test login' : function (browser) { @@ -18,13 +16,13 @@ module.exports = { EventBroadcaster.broadcast ( BroadcastMessages.STOP_COMPOSITE ); }).pause(1000); - clickButton(browser, tmpActionHandle); - - browser.pause(5000); + selectFrame(browser, '#tree treenode labelbox', function () { + browser.click('#tree treenode labelbox').pause(5000); + }); // browser.getLog('browser', function(logEntriesArray) { // logEntriesArray.forEach(function(log) { - // console.log('[' + log.level + '] ' + log.timestamp + ' : ' + log.message); + // console.log('[' + log.level + ']: ' + log.timestamp + ':\n' + log.message + '\n'); // }) // }); browser diff --git a/Website/test/selectFrame.js b/Website/test/selectFrame.js new file mode 100644 index 0000000000..ba21c0c3d5 --- /dev/null +++ b/Website/test/selectFrame.js @@ -0,0 +1,36 @@ +module.exports = function (browser, selector, activity) { + browser + .frame(null) + .execute(function (selector) { + function checkFrame(frame) { + var node = frame.document.querySelector(selector); + if (node) { + return []; + } else { + for (var i = 0; i < frame.length; i += 1) { + try { + var frameResult = checkFrame(frame[i]); + if (frameResult) { + frameResult.unshift(frame[i].name); + return frameResult; + } + } catch (_) {} + } + return null; + } + } + return checkFrame(window); + }, + [selector], + function (result) { + if (result.value) { + result.value.forEach(function (key) { + browser.frame(key); + }); + activity(); + browser.frame(null); + } else { + throw new Error('Did not find selector "' + selector + '" in any frame.'); + } + }); +} From ef2f2586ad1cbc45c089a9d5014bb1d09fa4df67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Wed, 15 Jun 2016 10:23:44 +0200 Subject: [PATCH 104/213] Added a page object for login, to experiment with getting that working. --- Website/test/minimal/min.e2e.js | 26 +++++++++++---------- Website/test/pageObjects/login.js | 39 +++++++++++++++++++++++++++++++ nightwatch.json | 2 +- 3 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 Website/test/pageObjects/login.js diff --git a/Website/test/minimal/min.e2e.js b/Website/test/minimal/min.e2e.js index b4cd3b603b..3f7ba25406 100644 --- a/Website/test/minimal/min.e2e.js +++ b/Website/test/minimal/min.e2e.js @@ -4,20 +4,22 @@ module.exports = { 'Demo test login' : function (browser) { browser .url('http://localhost:57917/Composite/top.aspx?mode=develop') - .waitForElementVisible('#id_username', 1000) - // .setValue('#id_username > box > input', 'admin') - // .setValue('#id_password > box > input', '123456') - .click('#loginButton') - .pause(1000) - .assert.visible('#appwindow'); - - browser - .execute(function () { - EventBroadcaster.broadcast ( BroadcastMessages.STOP_COMPOSITE ); - }).pause(1000); + .page.login() + .isShown() + .setUsername('admin') + .setPassword('123456') + .click('@submitButton') + .waitForElementNotVisible('@usernameField', 1000); + browser + .assert.visible('#appwindow') + .execute(function () { + EventBroadcaster.broadcast ( BroadcastMessages.STOP_COMPOSITE ); + }) + .pause(1000); + var treeNode; selectFrame(browser, '#tree treenode labelbox', function () { - browser.click('#tree treenode labelbox').pause(5000); + browser.click('#tree treenode labelbox'); }); // browser.getLog('browser', function(logEntriesArray) { diff --git a/Website/test/pageObjects/login.js b/Website/test/pageObjects/login.js new file mode 100644 index 0000000000..636a3eb773 --- /dev/null +++ b/Website/test/pageObjects/login.js @@ -0,0 +1,39 @@ +module.exports = { + url: '', + elements: [ + { usernameField: '#id_username' }, + { usernameInput: '#id_username > box > input' }, + { passwordField: '#id_password' }, + { passwordInput: '#id_password > box > input' }, + { submitButton: '#loginButton' } + ], + commands: [ + { + isShown: function () { + this.waitForElementVisible('@usernameField', 1000); + return this; + }, + setUsername: function (username) { + this + .clearValue('@usernameInput') // There may be a default value, clear it + .setValue('@usernameInput', username); + return this; + }, + setPassword: function (password) { + this + .clearValue('@passwordInput') // There may be a default value, clear it + .setValue('@passwordInput', password); + return this; + }, + fullLogin: function (username, password) { + this + .isShown() + .setUsername(username || 'admin') + .setPassword(password || '123456') + .click('@submitButton') + .waitForElementNotVisible('@usernameField', 1000); + return this; + } + } + ] +}; diff --git a/nightwatch.json b/nightwatch.json index 914261bfbf..a7645df2b4 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -3,7 +3,7 @@ "output_folder" : "reports", "custom_commands_path" : "", "custom_assertions_path" : "", - "page_objects_path" : "", + "page_objects_path" : "Website/test/pageObjects", "globals_path" : "", "selenium" : { From d59d847c3785483c099161b659f14bbf755fa5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Wed, 15 Jun 2016 14:01:05 +0200 Subject: [PATCH 105/213] Used page objects to access app window and start screen more easily. --- .../Composite/styles/default/controls.less | 5 +++- Website/test/minimal/min.e2e.js | 22 +++++++----------- Website/test/pageObjects/appWindow.js | 20 ++++++++++++++++ Website/test/pageObjects/startScreen.js | 23 +++++++++++++++++++ 4 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 Website/test/pageObjects/appWindow.js create mode 100644 Website/test/pageObjects/startScreen.js diff --git a/Website/Composite/styles/default/controls.less b/Website/Composite/styles/default/controls.less index 789210cd65..16b1a12597 100644 --- a/Website/Composite/styles/default/controls.less +++ b/Website/Composite/styles/default/controls.less @@ -6,7 +6,7 @@ ui|controlgroup { right: 0; top: 0; - z-index: 6; + z-index: 6; } ui|control { @@ -15,12 +15,15 @@ ui|control { &[controltype="close"] { right: 35px; top: 6px; + height: 30px; + width: 30px; ui|labelbody { &:after { content: '\00d7'; position: absolute; + right: -30px; text-align: center; width: 30px; height: 30px; diff --git a/Website/test/minimal/min.e2e.js b/Website/test/minimal/min.e2e.js index 3f7ba25406..8a8ceb9bf2 100644 --- a/Website/test/minimal/min.e2e.js +++ b/Website/test/minimal/min.e2e.js @@ -3,19 +3,13 @@ var selectFrame = require('../selectFrame.js'); module.exports = { 'Demo test login' : function (browser) { browser - .url('http://localhost:57917/Composite/top.aspx?mode=develop') - .page.login() - .isShown() - .setUsername('admin') - .setPassword('123456') - .click('@submitButton') - .waitForElementNotVisible('@usernameField', 1000); - browser - .assert.visible('#appwindow') - .execute(function () { - EventBroadcaster.broadcast ( BroadcastMessages.STOP_COMPOSITE ); - }) - .pause(1000); + .url('http://localhost:57917/Composite/top.aspx'); + browser.page.login().fullLogin(); + browser.page.startScreen().close(); + + browser.pause(1000); + browser.page.appWindow().enter(); + browser.assert.hidden('window[url="/Composite/content/views/start/start.aspx"] iframe'); var treeNode; selectFrame(browser, '#tree treenode labelbox', function () { @@ -24,7 +18,7 @@ module.exports = { // browser.getLog('browser', function(logEntriesArray) { // logEntriesArray.forEach(function(log) { - // console.log('[' + log.level + ']: ' + log.timestamp + ':\n' + log.message + '\n'); + // console.log('[' + log.level + ']: ' + log.timestamp + ':\n' + log.message); // }) // }); browser diff --git a/Website/test/pageObjects/appWindow.js b/Website/test/pageObjects/appWindow.js new file mode 100644 index 0000000000..b01d0eb9b3 --- /dev/null +++ b/Website/test/pageObjects/appWindow.js @@ -0,0 +1,20 @@ +module.exports = { + elements: [ + { appWindow: '#appwindow' }, + { appFrame: '#appwindow iframe' } + ], + commands: [ + { + enter: function () { + this.api + .frame(null); + this + .waitForElementPresent('@appWindow', 1000) + .getAttribute('@appFrame', 'id', function (result) { + this.api.frame(result.value) + }.bind(this)); + return this; + } + } + ] +}; diff --git a/Website/test/pageObjects/startScreen.js b/Website/test/pageObjects/startScreen.js new file mode 100644 index 0000000000..1a9bd11986 --- /dev/null +++ b/Website/test/pageObjects/startScreen.js @@ -0,0 +1,23 @@ +module.exports = { + elements: [ + { startFrame: 'window[url="/Composite/content/views/start/start.aspx"] iframe' }, + { closeButton: '#closecontrol' } + ], + commands: [ + { + enter: function () { + this.api.page.appWindow().enter(); // Start page shows inside appwindow. + this.waitForElementPresent('@startFrame', 1000); + this.api.pause(1000); + // Enter the frame containing it + this.getAttribute('@startFrame', 'id', function (result) { + this.api.frame(result.value) + }.bind(this)); + }, + close: function () { + this.enter(); + this.click('@closeButton'); + } + } + ] +}; From 8d4dd94892d2fbf36fc7bb39ed163fab1b43a293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Thu, 16 Jun 2016 11:22:33 +0200 Subject: [PATCH 106/213] Streamlined some methods. --- Website/test/{ => nightwatch}/minimal/min.e2e.js | 10 +++++----- Website/test/{ => nightwatch}/pageObjects/appWindow.js | 0 Website/test/{ => nightwatch}/pageObjects/login.js | 0 .../test/{ => nightwatch}/pageObjects/startScreen.js | 10 +++++++++- Website/test/{ => nightwatch}/selectFrame.js | 0 nightwatch.json | 10 +++++----- 6 files changed, 19 insertions(+), 11 deletions(-) rename Website/test/{ => nightwatch}/minimal/min.e2e.js (69%) rename Website/test/{ => nightwatch}/pageObjects/appWindow.js (100%) rename Website/test/{ => nightwatch}/pageObjects/login.js (100%) rename Website/test/{ => nightwatch}/pageObjects/startScreen.js (62%) rename Website/test/{ => nightwatch}/selectFrame.js (100%) diff --git a/Website/test/minimal/min.e2e.js b/Website/test/nightwatch/minimal/min.e2e.js similarity index 69% rename from Website/test/minimal/min.e2e.js rename to Website/test/nightwatch/minimal/min.e2e.js index 8a8ceb9bf2..b5cfb2c07a 100644 --- a/Website/test/minimal/min.e2e.js +++ b/Website/test/nightwatch/minimal/min.e2e.js @@ -2,14 +2,14 @@ var selectFrame = require('../selectFrame.js'); module.exports = { 'Demo test login' : function (browser) { - browser - .url('http://localhost:57917/Composite/top.aspx'); + browser.url('http://localhost:57917/Composite/top.aspx'); browser.page.login().fullLogin(); - browser.page.startScreen().close(); + var startScreen = browser.page.startScreen() + startScreen.isShown(); + startScreen.close(); browser.pause(1000); - browser.page.appWindow().enter(); - browser.assert.hidden('window[url="/Composite/content/views/start/start.aspx"] iframe'); + startScreen.isHidden(); var treeNode; selectFrame(browser, '#tree treenode labelbox', function () { diff --git a/Website/test/pageObjects/appWindow.js b/Website/test/nightwatch/pageObjects/appWindow.js similarity index 100% rename from Website/test/pageObjects/appWindow.js rename to Website/test/nightwatch/pageObjects/appWindow.js diff --git a/Website/test/pageObjects/login.js b/Website/test/nightwatch/pageObjects/login.js similarity index 100% rename from Website/test/pageObjects/login.js rename to Website/test/nightwatch/pageObjects/login.js diff --git a/Website/test/pageObjects/startScreen.js b/Website/test/nightwatch/pageObjects/startScreen.js similarity index 62% rename from Website/test/pageObjects/startScreen.js rename to Website/test/nightwatch/pageObjects/startScreen.js index 1a9bd11986..f1a41fb3d1 100644 --- a/Website/test/pageObjects/startScreen.js +++ b/Website/test/nightwatch/pageObjects/startScreen.js @@ -11,12 +11,20 @@ module.exports = { this.api.pause(1000); // Enter the frame containing it this.getAttribute('@startFrame', 'id', function (result) { - this.api.frame(result.value) + this.api.frame(result.value); }.bind(this)); }, close: function () { this.enter(); this.click('@closeButton'); + }, + isShown: function () { + this.api.page.appWindow().enter(); // Start page shows inside appwindow. + this.assert.visible('@startFrame'); + }, + isHidden: function () { + this.api.page.appWindow().enter(); // Start page shows inside appwindow. + this.assert.hidden('@startFrame'); } } ] diff --git a/Website/test/selectFrame.js b/Website/test/nightwatch/selectFrame.js similarity index 100% rename from Website/test/selectFrame.js rename to Website/test/nightwatch/selectFrame.js diff --git a/nightwatch.json b/nightwatch.json index a7645df2b4..9ec76ca19f 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -1,20 +1,20 @@ { - "src_folders" : ["Website/test/minimal"], + "src_folders" : ["Website/test/nightwatch/minimal"], "output_folder" : "reports", "custom_commands_path" : "", "custom_assertions_path" : "", - "page_objects_path" : "Website/test/pageObjects", + "page_objects_path" : "Website/test/nightwatch/pageObjects", "globals_path" : "", "selenium" : { "start_process" : true, - "server_path" : "Website/test/selenium-server-standalone-2.53.0.jar", + "server_path" : "Website/test/selenium/selenium-server-standalone-2.53.0.jar", "log_path" : "", "host" : "127.0.0.1", "port" : 4444, "cli_args" : { - "webdriver.chrome.driver" : "Website/test/chromedriver.exe", - "webdriver.ie.driver" : "Website/test/IEDriverServer.exe" + "webdriver.chrome.driver" : "Website/test/selenium/chromedriver.exe", + "webdriver.ie.driver" : "Website/test/selenium/IEDriverServer.exe" } }, From 1a9243ce458ff16ac8a4ee07d2caddd832d31ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 17 Jun 2016 14:19:59 +0200 Subject: [PATCH 107/213] Added a few tests of login screen. --- Website/test/nightwatch/login/devMode.e2e.js | 14 ++++++++++++++ Website/test/nightwatch/login/normalMode.e2e.js | 14 ++++++++++++++ Website/test/nightwatch/login/procedure.e2e.js | 16 ++++++++++++++++ Website/test/nightwatch/pageObjects/login.js | 7 +++---- nightwatch.json | 2 +- 5 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 Website/test/nightwatch/login/devMode.e2e.js create mode 100644 Website/test/nightwatch/login/normalMode.e2e.js create mode 100644 Website/test/nightwatch/login/procedure.e2e.js diff --git a/Website/test/nightwatch/login/devMode.e2e.js b/Website/test/nightwatch/login/devMode.e2e.js new file mode 100644 index 0000000000..04c7eb2aee --- /dev/null +++ b/Website/test/nightwatch/login/devMode.e2e.js @@ -0,0 +1,14 @@ +module.exports = { + 'Development mode has prefilled credentials': function (browser) { + browser.url('http://localhost:57917/Composite/top.aspx?mode=develop'); + login = browser.page.login(); + login.getAttribute('@usernameInput', 'value', function (result) { + browser.assert.equal(result.value, 'admin'); + }); + login.getAttribute('@passwordInput', 'value', function (result) { + browser.assert.equal(result.value, '123456'); + }); + + browser.end(); + } +}; diff --git a/Website/test/nightwatch/login/normalMode.e2e.js b/Website/test/nightwatch/login/normalMode.e2e.js new file mode 100644 index 0000000000..630f9ee58f --- /dev/null +++ b/Website/test/nightwatch/login/normalMode.e2e.js @@ -0,0 +1,14 @@ +module.exports = { + 'Normal mode does not have prefilled credentials': function (browser) { + browser.url('http://localhost:57917/Composite/top.aspx'); + login = browser.page.login(); + login.getAttribute('@usernameInput', 'value', function (result) { + browser.assert.equal(result.value, ''); + }); + login.getAttribute('@passwordInput', 'value', function (result) { + browser.assert.equal(result.value, ''); + }); + + browser.end(); + } +}; diff --git a/Website/test/nightwatch/login/procedure.e2e.js b/Website/test/nightwatch/login/procedure.e2e.js new file mode 100644 index 0000000000..8d8aac141a --- /dev/null +++ b/Website/test/nightwatch/login/procedure.e2e.js @@ -0,0 +1,16 @@ +module.exports = { + 'Can log in with correct credentials': function (browser) { + browser.url('http://localhost:57917/Composite/top.aspx'); + login = browser.page.login(); + startScreen = browser.page.startScreen(); + login + .setUsername('admin') + .setPassword('123456') + .click('@submitButton') + .waitForElementNotVisible('@usernameField', 1000); + // Check that start screen is shown + browser.page.appWindow().enter(); + startScreen.waitForElementVisible('@startFrame', 2000); + browser.end(); + } +}; diff --git a/Website/test/nightwatch/pageObjects/login.js b/Website/test/nightwatch/pageObjects/login.js index 636a3eb773..6aa21d9f6a 100644 --- a/Website/test/nightwatch/pageObjects/login.js +++ b/Website/test/nightwatch/pageObjects/login.js @@ -17,22 +17,21 @@ module.exports = { this .clearValue('@usernameInput') // There may be a default value, clear it .setValue('@usernameInput', username); - return this; + return this; }, setPassword: function (password) { this .clearValue('@passwordInput') // There may be a default value, clear it .setValue('@passwordInput', password); - return this; + return this; }, fullLogin: function (username, password) { - this + return this .isShown() .setUsername(username || 'admin') .setPassword(password || '123456') .click('@submitButton') .waitForElementNotVisible('@usernameField', 1000); - return this; } } ] diff --git a/nightwatch.json b/nightwatch.json index 9ec76ca19f..40a13cb330 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -1,5 +1,5 @@ { - "src_folders" : ["Website/test/nightwatch/minimal"], + "src_folders" : ["Website/test/nightwatch/login"], "output_folder" : "reports", "custom_commands_path" : "", "custom_assertions_path" : "", From 49e8b88a9ecbb364f225ebd920c5d625b5a60675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 17 Jun 2016 14:48:07 +0200 Subject: [PATCH 108/213] Moved test navigation to use config --- Website/test/nightwatch/login/devMode.e2e.js | 2 +- Website/test/nightwatch/login/normalMode.e2e.js | 2 +- Website/test/nightwatch/login/procedure.e2e.js | 2 +- nightwatch.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Website/test/nightwatch/login/devMode.e2e.js b/Website/test/nightwatch/login/devMode.e2e.js index 04c7eb2aee..49e5aa8d47 100644 --- a/Website/test/nightwatch/login/devMode.e2e.js +++ b/Website/test/nightwatch/login/devMode.e2e.js @@ -1,6 +1,6 @@ module.exports = { 'Development mode has prefilled credentials': function (browser) { - browser.url('http://localhost:57917/Composite/top.aspx?mode=develop'); + browser.url(browser.launchUrl + '/Composite/top.aspx?mode=develop'); login = browser.page.login(); login.getAttribute('@usernameInput', 'value', function (result) { browser.assert.equal(result.value, 'admin'); diff --git a/Website/test/nightwatch/login/normalMode.e2e.js b/Website/test/nightwatch/login/normalMode.e2e.js index 630f9ee58f..52affd2088 100644 --- a/Website/test/nightwatch/login/normalMode.e2e.js +++ b/Website/test/nightwatch/login/normalMode.e2e.js @@ -1,6 +1,6 @@ module.exports = { 'Normal mode does not have prefilled credentials': function (browser) { - browser.url('http://localhost:57917/Composite/top.aspx'); + browser.url(browser.launchUrl + '/Composite/top.aspx'); login = browser.page.login(); login.getAttribute('@usernameInput', 'value', function (result) { browser.assert.equal(result.value, ''); diff --git a/Website/test/nightwatch/login/procedure.e2e.js b/Website/test/nightwatch/login/procedure.e2e.js index 8d8aac141a..cb581b96b6 100644 --- a/Website/test/nightwatch/login/procedure.e2e.js +++ b/Website/test/nightwatch/login/procedure.e2e.js @@ -1,6 +1,6 @@ module.exports = { 'Can log in with correct credentials': function (browser) { - browser.url('http://localhost:57917/Composite/top.aspx'); + browser.url(browser.launchUrl + '/Composite/top.aspx'); login = browser.page.login(); startScreen = browser.page.startScreen(); login diff --git a/nightwatch.json b/nightwatch.json index 40a13cb330..e5b0d39b6f 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -20,7 +20,7 @@ "test_settings" : { "default" : { - "launch_url" : "http://localhost", + "launch_url" : "http://localhost:57917", "selenium_port" : 4444, "selenium_host" : "localhost", "silent": true, From c8430d3fb116d6b9d596b9fdf70f461b25477a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 17 Jun 2016 14:53:27 +0200 Subject: [PATCH 109/213] Renamed test files. --- Website/test/nightwatch/login/{devMode.e2e.js => devMode.js} | 0 .../test/nightwatch/login/{normalMode.e2e.js => normalMode.js} | 0 Website/test/nightwatch/login/{procedure.e2e.js => procedure.js} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename Website/test/nightwatch/login/{devMode.e2e.js => devMode.js} (100%) rename Website/test/nightwatch/login/{normalMode.e2e.js => normalMode.js} (100%) rename Website/test/nightwatch/login/{procedure.e2e.js => procedure.js} (100%) diff --git a/Website/test/nightwatch/login/devMode.e2e.js b/Website/test/nightwatch/login/devMode.js similarity index 100% rename from Website/test/nightwatch/login/devMode.e2e.js rename to Website/test/nightwatch/login/devMode.js diff --git a/Website/test/nightwatch/login/normalMode.e2e.js b/Website/test/nightwatch/login/normalMode.js similarity index 100% rename from Website/test/nightwatch/login/normalMode.e2e.js rename to Website/test/nightwatch/login/normalMode.js diff --git a/Website/test/nightwatch/login/procedure.e2e.js b/Website/test/nightwatch/login/procedure.js similarity index 100% rename from Website/test/nightwatch/login/procedure.e2e.js rename to Website/test/nightwatch/login/procedure.js From 5a50bd33d5f10e4b6b4d5156538ccaee165b19a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 17 Jun 2016 15:14:48 +0200 Subject: [PATCH 110/213] Fixed a minor CSS issue. --- Website/Composite/styles/default/controls.less | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Website/Composite/styles/default/controls.less b/Website/Composite/styles/default/controls.less index 16b1a12597..74d18cf525 100644 --- a/Website/Composite/styles/default/controls.less +++ b/Website/Composite/styles/default/controls.less @@ -13,7 +13,7 @@ ui|control { font-family: @font-family-monospace; &[controltype="close"] { - right: 35px; + right: 5px; top: 6px; height: 30px; width: 30px; @@ -23,7 +23,6 @@ ui|control { &:after { content: '\00d7'; position: absolute; - right: -30px; text-align: center; width: 30px; height: 30px; From 0cba93ce4aa11af17f443799bf27da9d6695bd7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 17 Jun 2016 15:21:17 +0200 Subject: [PATCH 111/213] Test that the start screen shows on login --- Website/test/nightwatch/pageObjects/startScreen.js | 14 ++++++-------- Website/test/nightwatch/startScreen/display.js | 10 ++++++++++ nightwatch.json | 5 ++++- 3 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 Website/test/nightwatch/startScreen/display.js diff --git a/Website/test/nightwatch/pageObjects/startScreen.js b/Website/test/nightwatch/pageObjects/startScreen.js index f1a41fb3d1..7a4e8b91dc 100644 --- a/Website/test/nightwatch/pageObjects/startScreen.js +++ b/Website/test/nightwatch/pageObjects/startScreen.js @@ -5,6 +5,11 @@ module.exports = { ], commands: [ { + prepare: function () { + this.api.page.login().fullLogin(); + this.api.page.appWindow().enter(); // Start page shows inside appwindow. + return this; + }, enter: function () { this.api.page.appWindow().enter(); // Start page shows inside appwindow. this.waitForElementPresent('@startFrame', 1000); @@ -13,18 +18,11 @@ module.exports = { this.getAttribute('@startFrame', 'id', function (result) { this.api.frame(result.value); }.bind(this)); + return this; }, close: function () { this.enter(); this.click('@closeButton'); - }, - isShown: function () { - this.api.page.appWindow().enter(); // Start page shows inside appwindow. - this.assert.visible('@startFrame'); - }, - isHidden: function () { - this.api.page.appWindow().enter(); // Start page shows inside appwindow. - this.assert.hidden('@startFrame'); } } ] diff --git a/Website/test/nightwatch/startScreen/display.js b/Website/test/nightwatch/startScreen/display.js new file mode 100644 index 0000000000..c679359916 --- /dev/null +++ b/Website/test/nightwatch/startScreen/display.js @@ -0,0 +1,10 @@ +module.exports = { + 'Start screen shows, includes close button': function (browser) { + browser.url(browser.launchUrl + '/Composite/top.aspx'); + var startScreen = browser.page.startScreen(); + startScreen.prepare() + .enter() + .assert.visible('@closeButton'); + browser.end(); + } +}; diff --git a/nightwatch.json b/nightwatch.json index e5b0d39b6f..2a3cb39787 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -1,5 +1,8 @@ { - "src_folders" : ["Website/test/nightwatch/login"], + "src_folders" : [ + "Website/test/nightwatch/login", + "Website/test/nightwatch/startScreen" + ], "output_folder" : "reports", "custom_commands_path" : "", "custom_assertions_path" : "", From d2bfbbb7287a87ba089d5392846a30b66dcc1e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 17 Jun 2016 15:30:56 +0200 Subject: [PATCH 112/213] Test that start screen does not show when already logged in. --- Website/test/nightwatch/pageObjects/appWindow.js | 5 +++++ Website/test/nightwatch/startScreen/logged-in.js | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 Website/test/nightwatch/startScreen/logged-in.js diff --git a/Website/test/nightwatch/pageObjects/appWindow.js b/Website/test/nightwatch/pageObjects/appWindow.js index b01d0eb9b3..bf00a2287e 100644 --- a/Website/test/nightwatch/pageObjects/appWindow.js +++ b/Website/test/nightwatch/pageObjects/appWindow.js @@ -5,6 +5,11 @@ module.exports = { ], commands: [ { + prepare: function () { + this.api.page.login().fullLogin(); + this.api.page.startScreen().close(); + return this; + }, enter: function () { this.api .frame(null); diff --git a/Website/test/nightwatch/startScreen/logged-in.js b/Website/test/nightwatch/startScreen/logged-in.js new file mode 100644 index 0000000000..8efaaa6ec9 --- /dev/null +++ b/Website/test/nightwatch/startScreen/logged-in.js @@ -0,0 +1,11 @@ +module.exports = { + 'Start screen does not show when already logged in': function (browser) { + browser.url(browser.launchUrl + '/Composite/top.aspx'); + browser.page.appWindow().prepare(); + browser.refresh(); + browser.page.appWindow().enter(); + browser.page.startScreen() + .assert.elementNotPresent('@startFrame'); + browser.end(); + } +}; From 68b2e260896d34b739955190407786673210c46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 17 Jun 2016 16:03:00 +0200 Subject: [PATCH 113/213] Removed minimal test file, it's outdated. --- Website/test/nightwatch/minimal/min.e2e.js | 27 ---------------------- 1 file changed, 27 deletions(-) delete mode 100644 Website/test/nightwatch/minimal/min.e2e.js diff --git a/Website/test/nightwatch/minimal/min.e2e.js b/Website/test/nightwatch/minimal/min.e2e.js deleted file mode 100644 index b5cfb2c07a..0000000000 --- a/Website/test/nightwatch/minimal/min.e2e.js +++ /dev/null @@ -1,27 +0,0 @@ -var selectFrame = require('../selectFrame.js'); - -module.exports = { - 'Demo test login' : function (browser) { - browser.url('http://localhost:57917/Composite/top.aspx'); - browser.page.login().fullLogin(); - var startScreen = browser.page.startScreen() - startScreen.isShown(); - startScreen.close(); - - browser.pause(1000); - startScreen.isHidden(); - - var treeNode; - selectFrame(browser, '#tree treenode labelbox', function () { - browser.click('#tree treenode labelbox'); - }); - - // browser.getLog('browser', function(logEntriesArray) { - // logEntriesArray.forEach(function(log) { - // console.log('[' + log.level + ']: ' + log.timestamp + ':\n' + log.message); - // }) - // }); - browser - .end(); - } -} From 921afc5977056b8e49f35bb9e4f2bdc78fcf7727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Mon, 20 Jun 2016 09:39:37 +0200 Subject: [PATCH 114/213] Restructured tests --- .gitignore | 2 +- Website/package.json | 3 +++ .../{nightwatch => e2e}/pageObjects/appWindow.js | 0 .../test/{nightwatch => e2e}/pageObjects/login.js | 0 .../{nightwatch => e2e}/pageObjects/startScreen.js | 0 Website/test/{nightwatch => e2e}/selectFrame.js | 0 .../test/{nightwatch => e2e/suite}/login/devMode.js | 0 .../{nightwatch => e2e/suite}/login/normalMode.js | 0 .../{nightwatch => e2e/suite}/login/procedure.js | 0 .../suite}/startScreen/display.js | 0 .../suite}/startScreen/logged-in.js | 0 nightwatch.json | 13 ++++++------- 12 files changed, 10 insertions(+), 8 deletions(-) rename Website/test/{nightwatch => e2e}/pageObjects/appWindow.js (100%) rename Website/test/{nightwatch => e2e}/pageObjects/login.js (100%) rename Website/test/{nightwatch => e2e}/pageObjects/startScreen.js (100%) rename Website/test/{nightwatch => e2e}/selectFrame.js (100%) rename Website/test/{nightwatch => e2e/suite}/login/devMode.js (100%) rename Website/test/{nightwatch => e2e/suite}/login/normalMode.js (100%) rename Website/test/{nightwatch => e2e/suite}/login/procedure.js (100%) rename Website/test/{nightwatch => e2e/suite}/startScreen/display.js (100%) rename Website/test/{nightwatch => e2e/suite}/startScreen/logged-in.js (100%) diff --git a/.gitignore b/.gitignore index f668909242..2b1fe2df69 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,4 @@ /Packages/ selenium-debug.log -reports/ +/Website/test/e2e/reports/ diff --git a/Website/package.json b/Website/package.json index 315237a672..ee53733315 100644 --- a/Website/package.json +++ b/Website/package.json @@ -4,6 +4,7 @@ "devDependencies": { "JSONPath": "^0.10.0", "autoprefixer-core": "^5.2.1", + "chromedriver": "2.21.2", "csswring": "^3.0.5", "grunt": "~0.4.5", "grunt-contrib-copy": "0.8.2", @@ -11,8 +12,10 @@ "grunt-contrib-uglify": "^0.9.1", "grunt-contrib-watch": "~0.6.1", "grunt-postcss": "^0.5.5", + "iedriver": "2.53.1", "less": "^2.1.2", "nightwatch": "0.9.3", + "selenium-server-standalone-jar": "2.53.1", "xml2js": "^0.4.10" } } diff --git a/Website/test/nightwatch/pageObjects/appWindow.js b/Website/test/e2e/pageObjects/appWindow.js similarity index 100% rename from Website/test/nightwatch/pageObjects/appWindow.js rename to Website/test/e2e/pageObjects/appWindow.js diff --git a/Website/test/nightwatch/pageObjects/login.js b/Website/test/e2e/pageObjects/login.js similarity index 100% rename from Website/test/nightwatch/pageObjects/login.js rename to Website/test/e2e/pageObjects/login.js diff --git a/Website/test/nightwatch/pageObjects/startScreen.js b/Website/test/e2e/pageObjects/startScreen.js similarity index 100% rename from Website/test/nightwatch/pageObjects/startScreen.js rename to Website/test/e2e/pageObjects/startScreen.js diff --git a/Website/test/nightwatch/selectFrame.js b/Website/test/e2e/selectFrame.js similarity index 100% rename from Website/test/nightwatch/selectFrame.js rename to Website/test/e2e/selectFrame.js diff --git a/Website/test/nightwatch/login/devMode.js b/Website/test/e2e/suite/login/devMode.js similarity index 100% rename from Website/test/nightwatch/login/devMode.js rename to Website/test/e2e/suite/login/devMode.js diff --git a/Website/test/nightwatch/login/normalMode.js b/Website/test/e2e/suite/login/normalMode.js similarity index 100% rename from Website/test/nightwatch/login/normalMode.js rename to Website/test/e2e/suite/login/normalMode.js diff --git a/Website/test/nightwatch/login/procedure.js b/Website/test/e2e/suite/login/procedure.js similarity index 100% rename from Website/test/nightwatch/login/procedure.js rename to Website/test/e2e/suite/login/procedure.js diff --git a/Website/test/nightwatch/startScreen/display.js b/Website/test/e2e/suite/startScreen/display.js similarity index 100% rename from Website/test/nightwatch/startScreen/display.js rename to Website/test/e2e/suite/startScreen/display.js diff --git a/Website/test/nightwatch/startScreen/logged-in.js b/Website/test/e2e/suite/startScreen/logged-in.js similarity index 100% rename from Website/test/nightwatch/startScreen/logged-in.js rename to Website/test/e2e/suite/startScreen/logged-in.js diff --git a/nightwatch.json b/nightwatch.json index 2a3cb39787..f57272d8df 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -1,23 +1,22 @@ { "src_folders" : [ - "Website/test/nightwatch/login", - "Website/test/nightwatch/startScreen" + "Website/test/e2e/suite" ], - "output_folder" : "reports", + "output_folder" : "Website/test/e2e/reports", "custom_commands_path" : "", "custom_assertions_path" : "", - "page_objects_path" : "Website/test/nightwatch/pageObjects", + "page_objects_path" : "Website/test/e2e/pageObjects", "globals_path" : "", "selenium" : { "start_process" : true, - "server_path" : "Website/test/selenium/selenium-server-standalone-2.53.0.jar", + "server_path" : "Website/node_modules/selenium-server-standalone-jar/jar/selenium-server-standalone-2.53.1.jar", "log_path" : "", "host" : "127.0.0.1", "port" : 4444, "cli_args" : { - "webdriver.chrome.driver" : "Website/test/selenium/chromedriver.exe", - "webdriver.ie.driver" : "Website/test/selenium/IEDriverServer.exe" + "webdriver.chrome.driver" : "Website/node_modules/chromedriver/lib/chromedriver/chromedriver.exe", + "webdriver.ie.driver" : "Website/node_modules/iedriver/lib/iedriver/IEdriverServer.exe" } }, From a7516e8d876a7c0f5705d0202ac6bc1bdf73d3b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Tue, 21 Jun 2016 12:14:24 +0200 Subject: [PATCH 115/213] Added a couple of tests of app window structure --- Website/Composite/app.aspx | 14 +++++----- Website/test/e2e/pageObjects/appWindow.js | 7 +++-- Website/test/e2e/pageObjects/login.js | 2 +- .../test/e2e/suite/appWindow/has-elements.js | 13 ++++++++++ Website/test/e2e/suite/appWindow/stages.js | 26 +++++++++++++++++++ 5 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 Website/test/e2e/suite/appWindow/has-elements.js create mode 100644 Website/test/e2e/suite/appWindow/stages.js diff --git a/Website/Composite/app.aspx b/Website/Composite/app.aspx index cca11b87d0..f4ad815896 100644 --- a/Website/Composite/app.aspx +++ b/Website/Composite/app.aspx @@ -26,7 +26,7 @@ - + @@ -118,14 +118,14 @@ <%-- - + --%> - + @@ -147,11 +147,11 @@ <%-- - + --%> - + @@ -169,14 +169,14 @@ - + - + diff --git a/Website/test/e2e/pageObjects/appWindow.js b/Website/test/e2e/pageObjects/appWindow.js index bf00a2287e..ee7c20bd02 100644 --- a/Website/test/e2e/pageObjects/appWindow.js +++ b/Website/test/e2e/pageObjects/appWindow.js @@ -1,7 +1,10 @@ module.exports = { elements: [ { appWindow: '#appwindow' }, - { appFrame: '#appwindow iframe' } + { appFrame: '#appwindow iframe' }, + { menu: '#menubar' }, + { explorer: '#explorer' }, + { stage: '#stage' } ], commands: [ { @@ -16,7 +19,7 @@ module.exports = { this .waitForElementPresent('@appWindow', 1000) .getAttribute('@appFrame', 'id', function (result) { - this.api.frame(result.value) + this.api.frame(result.value); }.bind(this)); return this; } diff --git a/Website/test/e2e/pageObjects/login.js b/Website/test/e2e/pageObjects/login.js index 6aa21d9f6a..a0c2555b8c 100644 --- a/Website/test/e2e/pageObjects/login.js +++ b/Website/test/e2e/pageObjects/login.js @@ -10,7 +10,7 @@ module.exports = { commands: [ { isShown: function () { - this.waitForElementVisible('@usernameField', 1000); + this.waitForElementVisible('@usernameField', 1500); return this; }, setUsername: function (username) { diff --git a/Website/test/e2e/suite/appWindow/has-elements.js b/Website/test/e2e/suite/appWindow/has-elements.js new file mode 100644 index 0000000000..f592874f87 --- /dev/null +++ b/Website/test/e2e/suite/appWindow/has-elements.js @@ -0,0 +1,13 @@ +module.exports = { + 'menu, explorer, stage': function (browser) { + browser.url(browser.launchUrl + '/Composite/top.aspx'); + var app = browser.page.appWindow() + app.prepare() + .enter() + .assert.visible('@menu') + .assert.visible('@explorer') + .assert.visible('@stage'); + + browser.end(); + } +}; diff --git a/Website/test/e2e/suite/appWindow/stages.js b/Website/test/e2e/suite/appWindow/stages.js new file mode 100644 index 0000000000..905989228c --- /dev/null +++ b/Website/test/e2e/suite/appWindow/stages.js @@ -0,0 +1,26 @@ +module.exports = { + 'stages match explorer buttons': function (browser) { + browser.url(browser.launchUrl + '/Composite/top.aspx'); + var app = browser.page.appWindow(); + app.prepare().enter(); + browser.elements('css selector', '#explorer explorertoolbarbutton', function (result) { + var explorerButtons = result.value; + browser.elements('css selector', '#stagedecks stagedeck', function (result) { + var stages = result.value; + browser.assert.equal(explorerButtons.length, stages.length); + explorerButtons.forEach(function (button, index) { + browser.elementIdAttribute(button.ELEMENT, 'id', function (result) { + var buttonId = '#' + result.value; + browser.elementIdAttribute(stages[index].ELEMENT, 'id', function (result) { + var stageId = '#' + result.value; + browser + .click(buttonId) + .waitForElementVisible(stageId, 2000); + }); + }); + }); + }); + }); + browser.end(); + } +}; From f8b076610440734efae697005be59a9f886fa81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Wed, 22 Jun 2016 09:49:10 +0200 Subject: [PATCH 116/213] Added some pauses between button clicks in stage test. --- Website/test/e2e/commands/enterFrame.js | 20 +++++++++++++++++++ Website/test/e2e/pageObjects/appWindow.js | 23 +++++++++++++++++++++- Website/test/e2e/suite/appWindow/stages.js | 11 +++++++---- Website/test/e2e/suite/content/editor.js | 11 +++++++++++ nightwatch.json | 2 +- 5 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 Website/test/e2e/commands/enterFrame.js create mode 100644 Website/test/e2e/suite/content/editor.js diff --git a/Website/test/e2e/commands/enterFrame.js b/Website/test/e2e/commands/enterFrame.js new file mode 100644 index 0000000000..4f752e382d --- /dev/null +++ b/Website/test/e2e/commands/enterFrame.js @@ -0,0 +1,20 @@ +var util = require('util'); +var events = require('events'); + +function EnterFrame() { + events.EventEmitter.call(this); +} + +util.inherits(EnterFrame, events.EventEmitter); + +EnterFrame.prototype.command = function(selector) { + this.client.api.element('css selector', selector, result => { + this.client.api.frame( + result.value, + () => this.emit('complete') + ); + }); + return this.client.api; +}; + +module.exports = EnterFrame; diff --git a/Website/test/e2e/pageObjects/appWindow.js b/Website/test/e2e/pageObjects/appWindow.js index ee7c20bd02..fe2de3d919 100644 --- a/Website/test/e2e/pageObjects/appWindow.js +++ b/Website/test/e2e/pageObjects/appWindow.js @@ -1,3 +1,12 @@ +const PERSPECTIVES = { + content: 1, + media: 2, + data: 3, + layout: 4, + functions: 5, + systems: 6 +} + module.exports = { elements: [ { appWindow: '#appwindow' }, @@ -11,17 +20,29 @@ module.exports = { prepare: function () { this.api.page.login().fullLogin(); this.api.page.startScreen().close(); + this.api.frame(null); + this.waitForElementPresent('@appWindow', 1000) return this; }, enter: function () { this.api .frame(null); this - .waitForElementPresent('@appWindow', 1000) .getAttribute('@appFrame', 'id', function (result) { this.api.frame(result.value); }.bind(this)); return this; + }, + enterPerspective: function (id) { + if (typeof id === 'string') { + id = PERSPECTIVES[id]; + } + this + .enter() + .click('#explorer explorertoolbarbutton:nth-of-type(' + id + ')') + .waitForElementVisible('#stagedecks stagedeck:nth-child(' + id + ')', 1000) + .enterFrame('#stagedecks stagedeck:nth-child(' + id + ') iframe'); + return this; } } ] diff --git a/Website/test/e2e/suite/appWindow/stages.js b/Website/test/e2e/suite/appWindow/stages.js index 905989228c..f567223af7 100644 --- a/Website/test/e2e/suite/appWindow/stages.js +++ b/Website/test/e2e/suite/appWindow/stages.js @@ -1,19 +1,22 @@ + + module.exports = { 'stages match explorer buttons': function (browser) { browser.url(browser.launchUrl + '/Composite/top.aspx'); var app = browser.page.appWindow(); app.prepare().enter(); - browser.elements('css selector', '#explorer explorertoolbarbutton', function (result) { + browser.elements('css selector', '#explorer explorertoolbarbutton', result => { var explorerButtons = result.value; - browser.elements('css selector', '#stagedecks stagedeck', function (result) { + browser.elements('css selector', '#stagedecks stagedeck', result => { var stages = result.value; browser.assert.equal(explorerButtons.length, stages.length); explorerButtons.forEach(function (button, index) { - browser.elementIdAttribute(button.ELEMENT, 'id', function (result) { + browser.elementIdAttribute(button.ELEMENT, 'id', result => { var buttonId = '#' + result.value; - browser.elementIdAttribute(stages[index].ELEMENT, 'id', function (result) { + browser.elementIdAttribute(stages[index].ELEMENT, 'id', result => { var stageId = '#' + result.value; browser + .pause(500) .click(buttonId) .waitForElementVisible(stageId, 2000); }); diff --git a/Website/test/e2e/suite/content/editor.js b/Website/test/e2e/suite/content/editor.js new file mode 100644 index 0000000000..1a26a824f6 --- /dev/null +++ b/Website/test/e2e/suite/content/editor.js @@ -0,0 +1,11 @@ +module.exports = { + 'can edit page': function (browser) { + browser.url(browser.launchUrl + '/Composite/top.aspx'); + var app = browser.page.appWindow(); + app.prepare() + .enterPerspective('content') + .waitForElementVisible('iframe[src="/Composite/content/views/browser/browser.aspx"]', 2000); + + browser.end(); + } +}; diff --git a/nightwatch.json b/nightwatch.json index f57272d8df..371869723c 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -3,7 +3,7 @@ "Website/test/e2e/suite" ], "output_folder" : "Website/test/e2e/reports", - "custom_commands_path" : "", + "custom_commands_path" : "Website/test/e2e/commands", "custom_assertions_path" : "", "page_objects_path" : "Website/test/e2e/pageObjects", "globals_path" : "", From 57ca8dea532eb0ba61f6a7a378f898eda53e4375 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Thu, 9 Jun 2016 15:40:37 +0300 Subject: [PATCH 117/213] fix opening external page to prevent browser popup blocker --- .../scripts/source/top/system/SystemAction.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Website/Composite/scripts/source/top/system/SystemAction.js b/Website/Composite/scripts/source/top/system/SystemAction.js index c5d22b01d7..8065597dbf 100644 --- a/Website/Composite/scripts/source/top/system/SystemAction.js +++ b/Website/Composite/scripts/source/top/system/SystemAction.js @@ -54,15 +54,14 @@ SystemAction.invoke = function ( action, arg ) { if ( node instanceof SystemNode ) { Application.lock ( SystemAction ); action.logger.debug ( "Execute \"" + action.getLabel () + "\" on \"" + node.getLabel () + "\"." ); - setTimeout ( function () { // timeout allow pressed buttons to unpress - TreeService.ExecuteSingleElementAction ( - node.getData (), - action.getHandle (), - Application.CONSOLE_ID - ); - MessageQueue.update(action.isSyncedRequest()); - Application.unlock ( SystemAction ); - }, 0 ); + + TreeService.ExecuteSingleElementAction ( + node.getData (), + action.getHandle (), + Application.CONSOLE_ID + ); + MessageQueue.update(action.isSyncedRequest()); + Application.unlock ( SystemAction ); } else { throw "Multiple actiontargets not supported."; } From b7680327395a5c33b2defe17e817a19666c99b78 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Fri, 10 Jun 2016 10:39:07 +0300 Subject: [PATCH 118/213] Fix popup blocker for double click --- .../source/top/ui/bindings/system/SystemToolBarBinding.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js b/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js index 3ffbe2abd2..620541f667 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js @@ -190,11 +190,8 @@ SystemToolBarBinding.prototype.handleBroadcast = function (broadcast, arg) { break; case BroadcastMessages.INVOKE_DEFAULT_ACTION: - var self = this; if (arg != null && arg.syncHandle == this.getSyncHandle()) { - setTimeout(function() { // timeout because binding attachment may happen now - self._invokeDefaultAction(); - }, 0); + this._invokeDefaultAction(); } break; } From d21b9db165e68fa62f94604892e277ed2f6a6a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Wed, 22 Jun 2016 18:54:00 +0200 Subject: [PATCH 119/213] Refined structure some. --- Website/test/e2e/pageObjects/editor.js | 18 ++++++++++++++++++ Website/test/e2e/suite/content/editor.js | 8 +++----- 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 Website/test/e2e/pageObjects/editor.js diff --git a/Website/test/e2e/pageObjects/editor.js b/Website/test/e2e/pageObjects/editor.js new file mode 100644 index 0000000000..c5e8032774 --- /dev/null +++ b/Website/test/e2e/pageObjects/editor.js @@ -0,0 +1,18 @@ +module.exports = { + elements: [ + { browserFrame: 'iframe[src="/Composite/content/views/browser/browser.aspx"]' }, + { treeFrame: 'iframe[src="/Composite/content/views/systemview/systemview.aspx"]' } + ], + commands: [ + { + prepare: function () { + var app = this.api.page.appWindow(); + app.prepare() + .enterPerspective('content'); + this + .waitForElementVisible('@browserFrame', 2000) + .enterFrame('@browserFrame'); + } + } + ] +}; diff --git a/Website/test/e2e/suite/content/editor.js b/Website/test/e2e/suite/content/editor.js index 1a26a824f6..5f7bd76db8 100644 --- a/Website/test/e2e/suite/content/editor.js +++ b/Website/test/e2e/suite/content/editor.js @@ -1,11 +1,9 @@ module.exports = { 'can edit page': function (browser) { browser.url(browser.launchUrl + '/Composite/top.aspx'); - var app = browser.page.appWindow(); - app.prepare() - .enterPerspective('content') - .waitForElementVisible('iframe[src="/Composite/content/views/browser/browser.aspx"]', 2000); - + var editor = browser.page.editor(); + editor.prepare() + .enterFrame('@treeFrame'); browser.end(); } }; From b067189a64fdb86c0cb3f92db04f02cd4a18463a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Thu, 23 Jun 2016 09:27:40 +0200 Subject: [PATCH 120/213] Renamed page object for content perspective --- Website/test/e2e/pageObjects/{editor.js => content.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Website/test/e2e/pageObjects/{editor.js => content.js} (100%) diff --git a/Website/test/e2e/pageObjects/editor.js b/Website/test/e2e/pageObjects/content.js similarity index 100% rename from Website/test/e2e/pageObjects/editor.js rename to Website/test/e2e/pageObjects/content.js From 406cd51a4be1f2d0863f02f7aa36742bbc777b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 24 Jun 2016 11:31:07 +0200 Subject: [PATCH 121/213] Finagled getting into the editor window, and back out again. --- Website/test/e2e/commands/leaveFrame.js | 14 +++++++++++ Website/test/e2e/commands/topFrame.js | 14 +++++++++++ Website/test/e2e/pageObjects/appWindow.js | 12 ++++----- Website/test/e2e/pageObjects/content.js | 28 +++++++++++++++------ Website/test/e2e/pageObjects/startScreen.js | 5 +--- Website/test/e2e/suite/content/editor.js | 20 ++++++++++++--- 6 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 Website/test/e2e/commands/leaveFrame.js create mode 100644 Website/test/e2e/commands/topFrame.js diff --git a/Website/test/e2e/commands/leaveFrame.js b/Website/test/e2e/commands/leaveFrame.js new file mode 100644 index 0000000000..be678cb994 --- /dev/null +++ b/Website/test/e2e/commands/leaveFrame.js @@ -0,0 +1,14 @@ +var util = require('util'); +var events = require('events'); + +function LeaveFrame() { + events.EventEmitter.call(this); +} + +util.inherits(LeaveFrame, events.EventEmitter); + +LeaveFrame.prototype.command = function() { + return this.client.api.frameParent(() => this.emit('complete')); +}; + +module.exports = LeaveFrame; diff --git a/Website/test/e2e/commands/topFrame.js b/Website/test/e2e/commands/topFrame.js new file mode 100644 index 0000000000..ab21e7a263 --- /dev/null +++ b/Website/test/e2e/commands/topFrame.js @@ -0,0 +1,14 @@ +var util = require('util'); +var events = require('events'); + +function TopFrame() { + events.EventEmitter.call(this); +} + +util.inherits(TopFrame, events.EventEmitter); + +TopFrame.prototype.command = function(selector) { + return this.client.api.frame(null, () => this.emit('complete')); +}; + +module.exports = TopFrame; diff --git a/Website/test/e2e/pageObjects/appWindow.js b/Website/test/e2e/pageObjects/appWindow.js index fe2de3d919..b51fa34070 100644 --- a/Website/test/e2e/pageObjects/appWindow.js +++ b/Website/test/e2e/pageObjects/appWindow.js @@ -20,17 +20,15 @@ module.exports = { prepare: function () { this.api.page.login().fullLogin(); this.api.page.startScreen().close(); - this.api.frame(null); - this.waitForElementPresent('@appWindow', 1000) + this + .topFrame() + .waitForElementPresent('@appWindow', 1000) return this; }, enter: function () { - this.api - .frame(null); this - .getAttribute('@appFrame', 'id', function (result) { - this.api.frame(result.value); - }.bind(this)); + .topFrame() + .enterFrame('@appFrame'); return this; }, enterPerspective: function (id) { diff --git a/Website/test/e2e/pageObjects/content.js b/Website/test/e2e/pageObjects/content.js index c5e8032774..e76a9f221f 100644 --- a/Website/test/e2e/pageObjects/content.js +++ b/Website/test/e2e/pageObjects/content.js @@ -1,17 +1,31 @@ module.exports = { + sections: { + docktabs: { + selector: 'dock[reference="main"] docktabs', + commands: [{ + closeTab: function (index) { + this.click('docktab:nth-of-type(' + index + ') control[controltype="close"]'); + } + }] + } + }, elements: [ - { browserFrame: 'iframe[src="/Composite/content/views/browser/browser.aspx"]' }, - { treeFrame: 'iframe[src="/Composite/content/views/systemview/systemview.aspx"]' } + { browserFrame: 'iframe[src="/Composite/content/views/browser/browser.aspx"]' } ], commands: [ { - prepare: function () { - var app = this.api.page.appWindow(); - app.prepare() + enter: function () { + this.api.page.appWindow() + .enter() .enterPerspective('content'); + return this; + }, + prepare: function () { + this.api.page.appWindow().prepare(); this - .waitForElementVisible('@browserFrame', 2000) - .enterFrame('@browserFrame'); + .enter() + .waitForElementVisible('@browserFrame', 2000); + return this; } } ] diff --git a/Website/test/e2e/pageObjects/startScreen.js b/Website/test/e2e/pageObjects/startScreen.js index 7a4e8b91dc..57b0428fcb 100644 --- a/Website/test/e2e/pageObjects/startScreen.js +++ b/Website/test/e2e/pageObjects/startScreen.js @@ -14,10 +14,7 @@ module.exports = { this.api.page.appWindow().enter(); // Start page shows inside appwindow. this.waitForElementPresent('@startFrame', 1000); this.api.pause(1000); - // Enter the frame containing it - this.getAttribute('@startFrame', 'id', function (result) { - this.api.frame(result.value); - }.bind(this)); + this.enterFrame('@startFrame'); // Enter the frame containing it return this; }, close: function () { diff --git a/Website/test/e2e/suite/content/editor.js b/Website/test/e2e/suite/content/editor.js index 5f7bd76db8..adfbf1f60f 100644 --- a/Website/test/e2e/suite/content/editor.js +++ b/Website/test/e2e/suite/content/editor.js @@ -1,9 +1,21 @@ module.exports = { - 'can edit page': function (browser) { + 'can open editor': function (browser) { browser.url(browser.launchUrl + '/Composite/top.aspx'); - var editor = browser.page.editor(); - editor.prepare() - .enterFrame('@treeFrame'); + var content = browser.page.content(); + content + .prepare() + .section.docktabs.click('docktab:nth-of-type(1)'); // Select first tab + content + .enterFrame('@browserFrame') + .click('toolbarbutton[image="page-edit-page"]') // Click edit button + .leaveFrame() + .waitForElementPresent('view:nth-of-type(2) window', 1000) // Locate and check editor screen - relies on it being the second ui:window tag in existence in this frame. + .enterFrame('view:nth-of-type(2) window iframe') + .waitForElementVisible('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]', 1000); + content + .enter() + .section.docktabs.closeTab(2); + browser.pause(500); browser.end(); } }; From 0256707bc1b9df02a7d996163b85cb2863f49b4b Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Fri, 24 Jun 2016 15:20:57 +0200 Subject: [PATCH 122/213] add 'qa' attributes --- .../scripts/source/top/core/Application.js | 6 +++++- .../scripts/source/top/ui/bindings/Binding.js | 18 ++++++++++++++++-- .../top/ui/bindings/docks/DockBinding.js | 3 +++ .../bindings/explorer/ExplorerMenuBinding.js | 3 +++ .../bindings/stage/decks/StageDecksBinding.js | 3 +++ .../ui/bindings/system/SystemToolBarBinding.js | 3 +++ 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Website/Composite/scripts/source/top/core/Application.js b/Website/Composite/scripts/source/top/core/Application.js index 658a2bd3d3..f50ff95b2e 100644 --- a/Website/Composite/scripts/source/top/core/Application.js +++ b/Website/Composite/scripts/source/top/core/Application.js @@ -127,8 +127,12 @@ _Application.prototype = { * Be advised, however, that this setup is highly dysfunctional in IE. * @type {boolean} */ - isBlurred : false, + isBlurred: false, + /** + * @type {boolean} + */ + isTestEnvironment: true, // PRIVATE .......................................................... diff --git a/Website/Composite/scripts/source/top/ui/bindings/Binding.js b/Website/Composite/scripts/source/top/ui/bindings/Binding.js index 33701fccb9..0b3e51f881 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/Binding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/Binding.js @@ -466,7 +466,8 @@ Binding.prototype.onBindingAttach = function () { if ( !this.bindingElement.parentNode ) { alert ( this + " onBindingAttach: Binding must be positioned in document structure before attachment can be invoked." ); } else { - this.boxObject = new BindingBoxObject ( this ); + this.boxObject = new BindingBoxObject(this); + this._initializeBindingTestFeatures(); this._initializeBindingPersistanceFeatures (); this._initializeBindingGeneralFeatures (); this._initializeBindingDragAndDropFeatures (); @@ -610,6 +611,19 @@ Binding.prototype.registerDependentBinding = function ( binding ) { this.dependentBindings [ binding.key ] = binding; } +/** + * Initialize attributes for test. + */ +Binding.prototype._initializeBindingTestFeatures = function () { + + if (Application.isTestEnvironment) { + var label = this.getProperty("label"); + if (label && label.indexOf("${string:") > -1) { + this.setProperty("data-qa-label", label); + } + } +} + /** * Initialize persistance. */ @@ -1897,4 +1911,4 @@ Binding.newInstance = function ( ownerDocument ) { var element = DOMUtil.createElementNS ( Constants.NS_UI, "ui:binding", ownerDocument ); return UserInterface.registerBinding ( element, Binding ); -} +} diff --git a/Website/Composite/scripts/source/top/ui/bindings/docks/DockBinding.js b/Website/Composite/scripts/source/top/ui/bindings/docks/DockBinding.js index 563db4e82c..5776e0d825 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/docks/DockBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/docks/DockBinding.js @@ -236,6 +236,9 @@ DockBinding.prototype.prepareNewView = function ( definition ) { if (definition.isPinned) { tabBinding.setProperty("pinned", true); } + if (Application.isTestEnvironment) { + tabBinding.setProperty("data-qa-token", definition.entityToken); + } this.appendTabByBindings ( tabBinding, null ); // listen for dirty events and loaded pages diff --git a/Website/Composite/scripts/source/top/ui/bindings/explorer/ExplorerMenuBinding.js b/Website/Composite/scripts/source/top/ui/bindings/explorer/ExplorerMenuBinding.js index 8f507d3561..0544d879ac 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/explorer/ExplorerMenuBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/explorer/ExplorerMenuBinding.js @@ -126,6 +126,9 @@ ExplorerMenuBinding.prototype._mountMinButton = function (definition) { button.setToolTip(definition.label); // use label as tooltip button.handle = definition.handle; button.node = definition.node; + if (Application.isTestEnvironment) { + button.setProperty("data-qa-perspective", definition.node.getTag()); + } this._group.add(button); this._list.add(button); button.attach(); diff --git a/Website/Composite/scripts/source/top/ui/bindings/stage/decks/StageDecksBinding.js b/Website/Composite/scripts/source/top/ui/bindings/stage/decks/StageDecksBinding.js index 0ac5e3213c..92c9a7489b 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/stage/decks/StageDecksBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/stage/decks/StageDecksBinding.js @@ -48,6 +48,9 @@ StageDecksBinding.prototype.mountDefinition = function ( definition ) { deckBinding.handle = definition.handle; deckBinding.perspectiveNode = definition.node; deckBinding.definition = definition; + if (Application.isTestEnvironment) { + deckBinding.setProperty("data-qa-perspective", definition.node.getTag()); + } this._decks [ deckBinding.handle ] = deckBinding; this.add ( deckBinding ); deckBinding.attach (); diff --git a/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js b/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js index 620541f667..5526a9c968 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js @@ -419,6 +419,9 @@ SystemToolBarBinding.prototype.getToolBarButtonBinding = function ( action) { if ( action.isDisabled ()) { binding.disable (); } + if (Application.isTestEnvironment) { + binding.setProperty("data-qa-key", action.getKey()); + } /* * Stamp the action as a property on the buttonbinding From 8a99a444763f7bedac8d6ee33e7dfaaca3c3d40c Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Fri, 24 Jun 2016 16:02:15 +0200 Subject: [PATCH 123/213] update 'qa' attributes names --- Website/Composite/scripts/source/top/ui/bindings/Binding.js | 2 +- .../scripts/source/top/ui/bindings/docks/DockBinding.js | 2 +- .../source/top/ui/bindings/explorer/ExplorerMenuBinding.js | 2 +- .../source/top/ui/bindings/stage/decks/StageDecksBinding.js | 2 +- .../source/top/ui/bindings/system/SystemToolBarBinding.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Website/Composite/scripts/source/top/ui/bindings/Binding.js b/Website/Composite/scripts/source/top/ui/bindings/Binding.js index 0b3e51f881..d5a4b21c8a 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/Binding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/Binding.js @@ -619,7 +619,7 @@ Binding.prototype._initializeBindingTestFeatures = function () { if (Application.isTestEnvironment) { var label = this.getProperty("label"); if (label && label.indexOf("${string:") > -1) { - this.setProperty("data-qa-label", label); + this.setProperty("data-qa", label); } } } diff --git a/Website/Composite/scripts/source/top/ui/bindings/docks/DockBinding.js b/Website/Composite/scripts/source/top/ui/bindings/docks/DockBinding.js index 5776e0d825..9ef89b846d 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/docks/DockBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/docks/DockBinding.js @@ -237,7 +237,7 @@ DockBinding.prototype.prepareNewView = function ( definition ) { tabBinding.setProperty("pinned", true); } if (Application.isTestEnvironment) { - tabBinding.setProperty("data-qa-token", definition.entityToken); + tabBinding.setProperty("data-qa", definition.entityToken); } this.appendTabByBindings ( tabBinding, null ); diff --git a/Website/Composite/scripts/source/top/ui/bindings/explorer/ExplorerMenuBinding.js b/Website/Composite/scripts/source/top/ui/bindings/explorer/ExplorerMenuBinding.js index 0544d879ac..3fb022d11d 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/explorer/ExplorerMenuBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/explorer/ExplorerMenuBinding.js @@ -127,7 +127,7 @@ ExplorerMenuBinding.prototype._mountMinButton = function (definition) { button.handle = definition.handle; button.node = definition.node; if (Application.isTestEnvironment) { - button.setProperty("data-qa-perspective", definition.node.getTag()); + button.setProperty("data-qa", definition.node.getTag()); } this._group.add(button); this._list.add(button); diff --git a/Website/Composite/scripts/source/top/ui/bindings/stage/decks/StageDecksBinding.js b/Website/Composite/scripts/source/top/ui/bindings/stage/decks/StageDecksBinding.js index 92c9a7489b..3a4b1021df 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/stage/decks/StageDecksBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/stage/decks/StageDecksBinding.js @@ -49,7 +49,7 @@ StageDecksBinding.prototype.mountDefinition = function ( definition ) { deckBinding.perspectiveNode = definition.node; deckBinding.definition = definition; if (Application.isTestEnvironment) { - deckBinding.setProperty("data-qa-perspective", definition.node.getTag()); + deckBinding.setProperty("data-qa", "perspective" + definition.node.getTag()); } this._decks [ deckBinding.handle ] = deckBinding; this.add ( deckBinding ); diff --git a/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js b/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js index 5526a9c968..c16d0aff65 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/system/SystemToolBarBinding.js @@ -420,7 +420,7 @@ SystemToolBarBinding.prototype.getToolBarButtonBinding = function ( action) { binding.disable (); } if (Application.isTestEnvironment) { - binding.setProperty("data-qa-key", action.getKey()); + binding.setProperty("data-qa", action.getKey()); } /* From 1cc9ab48284a391687b1ddd6837b4f35acd34a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 24 Jun 2016 12:59:00 +0200 Subject: [PATCH 124/213] Added selectFrame function to NW API. --- .../test/e2e/{ => commands}/selectFrame.js | 31 ++++++++++++------- Website/test/e2e/suite/content/editor.js | 4 ++- 2 files changed, 23 insertions(+), 12 deletions(-) rename Website/test/e2e/{ => commands}/selectFrame.js (55%) diff --git a/Website/test/e2e/selectFrame.js b/Website/test/e2e/commands/selectFrame.js similarity index 55% rename from Website/test/e2e/selectFrame.js rename to Website/test/e2e/commands/selectFrame.js index ba21c0c3d5..aece8292ea 100644 --- a/Website/test/e2e/selectFrame.js +++ b/Website/test/e2e/commands/selectFrame.js @@ -1,6 +1,15 @@ -module.exports = function (browser, selector, activity) { - browser - .frame(null) +var util = require('util'); +var events = require('events'); + +function SelectFrame() { + events.EventEmitter.call(this); +} + +util.inherits(SelectFrame, events.EventEmitter); + +SelectFrame.prototype.command = function(selector) { + this.client.api + .topFrame() .execute(function (selector) { function checkFrame(frame) { var node = frame.document.querySelector(selector); @@ -11,7 +20,7 @@ module.exports = function (browser, selector, activity) { try { var frameResult = checkFrame(frame[i]); if (frameResult) { - frameResult.unshift(frame[i].name); + frameResult.unshift(i); return frameResult; } } catch (_) {} @@ -22,15 +31,15 @@ module.exports = function (browser, selector, activity) { return checkFrame(window); }, [selector], - function (result) { + result => { if (result.value) { - result.value.forEach(function (key) { - browser.frame(key); - }); - activity(); - browser.frame(null); + result.value.forEach(key => this.client.api.frame(key)); + this.emit('complete'); } else { throw new Error('Did not find selector "' + selector + '" in any frame.'); } }); -} + return this.client.api; +}; + +module.exports = SelectFrame; diff --git a/Website/test/e2e/suite/content/editor.js b/Website/test/e2e/suite/content/editor.js index adfbf1f60f..a8ea5b0c27 100644 --- a/Website/test/e2e/suite/content/editor.js +++ b/Website/test/e2e/suite/content/editor.js @@ -12,10 +12,12 @@ module.exports = { .waitForElementPresent('view:nth-of-type(2) window', 1000) // Locate and check editor screen - relies on it being the second ui:window tag in existence in this frame. .enterFrame('view:nth-of-type(2) window iframe') .waitForElementVisible('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]', 1000); + browser.pause(500); content + .selectFrame('#tinymce > img:nth-child(1)') // Find frame with editor content + .verify.visible('#tinymce > img:nth-child(2)') // Check that it has more than just one entry .enter() .section.docktabs.closeTab(2); - browser.pause(500); browser.end(); } }; From 5960dfa61ff537213909642aecb8623bc2e0ae52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 24 Jun 2016 17:00:21 +0200 Subject: [PATCH 125/213] Put some comments in editor test to make it make more sense. --- Website/test/e2e/suite/content/editor.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Website/test/e2e/suite/content/editor.js b/Website/test/e2e/suite/content/editor.js index a8ea5b0c27..5fde4e26d5 100644 --- a/Website/test/e2e/suite/content/editor.js +++ b/Website/test/e2e/suite/content/editor.js @@ -4,18 +4,30 @@ module.exports = { var content = browser.page.content(); content .prepare() - .section.docktabs.click('docktab:nth-of-type(1)'); // Select first tab + // Select first tab + .section.docktabs.click('docktab:nth-of-type(1)'); content .enterFrame('@browserFrame') - .click('toolbarbutton[image="page-edit-page"]') // Click edit button + // Click edit button (identified by icon name) + .click('toolbarbutton[image="page-edit-page"]') .leaveFrame() - .waitForElementPresent('view:nth-of-type(2) window', 1000) // Locate and check editor screen - relies on it being the second ui:window tag in existence in this frame. + // Locate and check editor screen + // relies on it being the second ui:window tag in existence in this frame. + .waitForElementPresent('view:nth-of-type(2) window', 1000) .enterFrame('view:nth-of-type(2) window iframe') .waitForElementVisible('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]', 1000); browser.pause(500); content - .selectFrame('#tinymce > img:nth-child(1)') // Find frame with editor content - .verify.visible('#tinymce > img:nth-child(2)') // Check that it has more than just one entry + // Find frame with editor content + .selectFrame('#tinymce > img:nth-child(1)') + // Check that it has more than just one entry + .verify.visible('#tinymce > img:nth-child(2)') + // Enter the element + .click('#tinymce > img:nth-child(2)') + browser + .pause(3000) + content + // Close editor after you .enter() .section.docktabs.closeTab(2); browser.end(); From d65b5c00a30a55bce1bf0ae2ab579848f32ad3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Mon, 27 Jun 2016 16:45:02 +0200 Subject: [PATCH 126/213] Can almost test editor. Fails to approve changes. --- Website/test/e2e/commands/selectFrame.js | 8 ++-- Website/test/e2e/suite/content/editor.js | 51 ++++++++++++++++++++---- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/Website/test/e2e/commands/selectFrame.js b/Website/test/e2e/commands/selectFrame.js index aece8292ea..e629761912 100644 --- a/Website/test/e2e/commands/selectFrame.js +++ b/Website/test/e2e/commands/selectFrame.js @@ -7,9 +7,11 @@ function SelectFrame() { util.inherits(SelectFrame, events.EventEmitter); -SelectFrame.prototype.command = function(selector) { +SelectFrame.prototype.command = function(selector, noReset) { + if (!noReset) { + this.client.api.topFrame() + } this.client.api - .topFrame() .execute(function (selector) { function checkFrame(frame) { var node = frame.document.querySelector(selector); @@ -32,7 +34,7 @@ SelectFrame.prototype.command = function(selector) { }, [selector], result => { - if (result.value) { + if (Array.isArray(result.value)) { result.value.forEach(key => this.client.api.frame(key)); this.emit('complete'); } else { diff --git a/Website/test/e2e/suite/content/editor.js b/Website/test/e2e/suite/content/editor.js index 5fde4e26d5..dcf505b4ba 100644 --- a/Website/test/e2e/suite/content/editor.js +++ b/Website/test/e2e/suite/content/editor.js @@ -22,14 +22,49 @@ module.exports = { .selectFrame('#tinymce > img:nth-child(1)') // Check that it has more than just one entry .verify.visible('#tinymce > img:nth-child(2)') - // Enter the element - .click('#tinymce > img:nth-child(2)') - browser - .pause(3000) + // Select the first element + .click('#tinymce > img:nth-child(1)') + // Click the toolbar button for properties + .selectFrame('toolbarbutton[cmd="compositeInsertRendering"]') + .click('toolbarbutton[cmd="compositeInsertRendering"]') + .api.pause(1000); + // Click the edit HTML button + content + .selectFrame('#renderingdialogpage') + .click('htmldatadialog') + .api.pause(1000); + // Find and enter the editor in the dialog that just appeared content - // Close editor after you - .enter() - .section.docktabs.closeTab(2); - browser.end(); + .selectFrame('#masterdialogset') + .selectFrame('#visualeditor', true) + .selectFrame('body#tinymce', true); + browser + .execute(function () { + var element = document.querySelector('#tinymce > h1 > em'); + element.innerHTML = 'Jupiter'; + }, [], () => { + // Approve the change + content + .selectFrame('#masterdialogset') + .selectFrame('#visualeditor', true) + .click('clickbutton[response="accept"]'); + // Close the properties dialog + browser.topFrame(); + content + .selectFrame('#renderingdialogpage') + .verify.elementPresent('#renderingdialogpage') + .verify.elementPresent('clickbutton[callbackid="buttonAccept"]') + .verify.visible('#renderingdialogpage') + .verify.visible('clickbutton[callbackid="buttonAccept"]') + .click('clickbutton[callbackid="buttonAccept"]') + browser + .pause(3000) + content + // Close editor after you + .enter() + .section.docktabs.closeTab(2); + // Check that the change is made + browser.end(); + }) } }; From 99e4284a35da18c96a504f9da9f4ce5b18b67423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Tue, 28 Jun 2016 14:51:24 +0200 Subject: [PATCH 127/213] Working test. Begun refactoring out parts to make reusable. --- Website/test/e2e/commands/replaceContent.js | 20 +++++ Website/test/e2e/commands/waitForFrameLoad.js | 52 ++++++++++++ Website/test/e2e/pageObjects/content.js | 1 - Website/test/e2e/suite/content/editor.js | 79 +++++++++++-------- 4 files changed, 119 insertions(+), 33 deletions(-) create mode 100644 Website/test/e2e/commands/replaceContent.js create mode 100644 Website/test/e2e/commands/waitForFrameLoad.js diff --git a/Website/test/e2e/commands/replaceContent.js b/Website/test/e2e/commands/replaceContent.js new file mode 100644 index 0000000000..122e8d27a3 --- /dev/null +++ b/Website/test/e2e/commands/replaceContent.js @@ -0,0 +1,20 @@ +var util = require('util'); +var events = require('events'); + +function ReplaceContent() { + events.EventEmitter.call(this); +} + +util.inherits(ReplaceContent, events.EventEmitter); + +ReplaceContent.prototype.command = function(selector, newContent) { + this.client.api.execute(function (selector, newContent) { + var element = document.querySelector(selector); + element.innerHTML = newContent; + }, [selector, newContent], () => { + this.emit('complete'); + }); + return this.client.api; +}; + +module.exports = ReplaceContent; diff --git a/Website/test/e2e/commands/waitForFrameLoad.js b/Website/test/e2e/commands/waitForFrameLoad.js new file mode 100644 index 0000000000..932e2b9b6c --- /dev/null +++ b/Website/test/e2e/commands/waitForFrameLoad.js @@ -0,0 +1,52 @@ +var util = require('util'); +var events = require('events'); + +function WaitForFrameLoad() { + events.EventEmitter.call(this); + this.abortOnFailure = typeof this.client.api.globals.abortOnAssertionFailure == 'undefined' || this.client.api.globals.abortOnAssertionFailure; +} + +util.inherits(WaitForFrameLoad, events.EventEmitter); + +WaitForFrameLoad.prototype.command = function(selector, timeout) { + if (!timeout) { + throw new Error('waitForFrameLoaded() must have timeout.'); + } + + const complete = () => this.emit('complete'); + var isTimedOut = false; + var interval = Math.max(Math.floor(timeout / 10), 5); + var timeElapsed = interval; + + const client = this.client; + + var timer = setTimeout(() => { + isTimedOut = true; + }, timeout); + + function checkFrameLoaded() { + client.api.execute(function (selector) { + var frame = document.querySelector(selector); + return frame.contentDocument.readyState === 'complete'; + }, [selector], (result) => { + console.log(result.value); + if (result.value) { + clearTimeout(timer); + client.assertion(true, null, null, 'Frame "' + selector + '" loaded within ' + timeElapsed + 'ms', this.abortOnFailure); + complete(); + } else { + if (isTimedOut) { + client.assertion(false, null, null, 'Frame "' + selector + '" did not load within ' + timeout + 'ms', this.abortOnFailure, this._stackTrace); + } else { + console.log('not yet', timeElapsed); + setTimeout(checkFrameLoaded, interval); + timeElapsed += interval; + } + } + }); + }; + checkFrameLoaded(); + return client.api; +}; + +module.exports = WaitForFrameLoad; diff --git a/Website/test/e2e/pageObjects/content.js b/Website/test/e2e/pageObjects/content.js index e76a9f221f..b1087fcb83 100644 --- a/Website/test/e2e/pageObjects/content.js +++ b/Website/test/e2e/pageObjects/content.js @@ -16,7 +16,6 @@ module.exports = { { enter: function () { this.api.page.appWindow() - .enter() .enterPerspective('content'); return this; }, diff --git a/Website/test/e2e/suite/content/editor.js b/Website/test/e2e/suite/content/editor.js index dcf505b4ba..df91166f17 100644 --- a/Website/test/e2e/suite/content/editor.js +++ b/Website/test/e2e/suite/content/editor.js @@ -12,11 +12,11 @@ module.exports = { .click('toolbarbutton[image="page-edit-page"]') .leaveFrame() // Locate and check editor screen - // relies on it being the second ui:window tag in existence in this frame. + // relies on it being inside the second ui:view tag in existence in the content frame. .waitForElementPresent('view:nth-of-type(2) window', 1000) .enterFrame('view:nth-of-type(2) window iframe') - .waitForElementVisible('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]', 1000); - browser.pause(500); + .waitForElementVisible('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]', 1000) + .waitForFrameLoad('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]', 1000); content // Find frame with editor content .selectFrame('#tinymce > img:nth-child(1)') @@ -28,43 +28,58 @@ module.exports = { .selectFrame('toolbarbutton[cmd="compositeInsertRendering"]') .click('toolbarbutton[cmd="compositeInsertRendering"]') .api.pause(1000); - // Click the edit HTML button + // Click the edit HTML button content .selectFrame('#renderingdialogpage') .click('htmldatadialog') .api.pause(1000); - // Find and enter the editor in the dialog that just appeared + // Find and enter the editor in the dialog that just appeared content .selectFrame('#masterdialogset') .selectFrame('#visualeditor', true) .selectFrame('body#tinymce', true); + // Edit its contents + browser.replaceContent('#tinymce > h1 > em', 'Jupiter'); + // Approve the change + content + .selectFrame('#masterdialogset') + .selectFrame('#visualeditor', true) + .click('clickbutton[response="accept"]'); + // Close the properties dialog + browser.topFrame(); + content + .selectFrame('#renderingdialogpage') + .waitForElementVisible('clickbutton[callbackid="buttonAccept"]', 1000) + .click('clickbutton[callbackid="buttonAccept"] labelbox') + browser.pause(500) + // Save change. + content + .selectFrame('#savebutton'); + content + .getAttribute('#savebutton', 'isdisabled', function (result) { + browser.verify.equal(null, result.value); + }); + content + .click('#savebutton > labelbox'); + browser + .pause(1000) + content + .getAttribute('#savebutton', 'isdisabled', function (result) { + browser.verify.equal('true', result.value); + }); + content + // Close editor after you + .enter() + .section.docktabs.closeTab(2); + // Check that the change is made browser - .execute(function () { - var element = document.querySelector('#tinymce > h1 > em'); - element.innerHTML = 'Jupiter'; - }, [], () => { - // Approve the change - content - .selectFrame('#masterdialogset') - .selectFrame('#visualeditor', true) - .click('clickbutton[response="accept"]'); - // Close the properties dialog - browser.topFrame(); - content - .selectFrame('#renderingdialogpage') - .verify.elementPresent('#renderingdialogpage') - .verify.elementPresent('clickbutton[callbackid="buttonAccept"]') - .verify.visible('#renderingdialogpage') - .verify.visible('clickbutton[callbackid="buttonAccept"]') - .click('clickbutton[callbackid="buttonAccept"]') - browser - .pause(3000) - content - // Close editor after you - .enter() - .section.docktabs.closeTab(2); - // Check that the change is made - browser.end(); - }) + .pause(3000) + content + .enterFrame('@browserFrame') + .waitForElementVisible('#browsertabbox iframe', 1000) + .enterFrame('#browsertabbox iframe') + // The below fails if page starts out with unpublished changes. + .assert.containsText('div.jumbotron-content > h1 > em', 'Jupiter'); + browser.end(); } }; From a42cdeac4cd7b82604592dd1efd11fa533ffa931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Tue, 28 Jun 2016 16:40:24 +0200 Subject: [PATCH 128/213] Cleanup continues. --- Website/test/e2e/commands/waitForFrameLoad.js | 24 +++++++++---------- Website/test/e2e/pageObjects/content.js | 6 ++++- Website/test/e2e/suite/content/editor.js | 22 ++++++++++------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Website/test/e2e/commands/waitForFrameLoad.js b/Website/test/e2e/commands/waitForFrameLoad.js index 932e2b9b6c..0bd33ab827 100644 --- a/Website/test/e2e/commands/waitForFrameLoad.js +++ b/Website/test/e2e/commands/waitForFrameLoad.js @@ -17,11 +17,14 @@ WaitForFrameLoad.prototype.command = function(selector, timeout) { var isTimedOut = false; var interval = Math.max(Math.floor(timeout / 10), 5); var timeElapsed = interval; + var timedRecheck, timeoutTimer; const client = this.client; - var timer = setTimeout(() => { - isTimedOut = true; + timeoutTimer = setTimeout(() => { + clearTimeout(timedRecheck); + client.assertion(false, null, null, 'Frame <' + selector + '> did not load within ' + timeout + 'ms', this.abortOnFailure, this._stackTrace); + complete(); }, timeout); function checkFrameLoaded() { @@ -29,19 +32,14 @@ WaitForFrameLoad.prototype.command = function(selector, timeout) { var frame = document.querySelector(selector); return frame.contentDocument.readyState === 'complete'; }, [selector], (result) => { - console.log(result.value); - if (result.value) { - clearTimeout(timer); - client.assertion(true, null, null, 'Frame "' + selector + '" loaded within ' + timeElapsed + 'ms', this.abortOnFailure); + if (result.value === true) { + clearTimeout(timeoutTimer); + client.assertion(true, null, null, 'Frame <' + selector + '> loaded within ' + timeElapsed + 'ms', this.abortOnFailure); complete(); } else { - if (isTimedOut) { - client.assertion(false, null, null, 'Frame "' + selector + '" did not load within ' + timeout + 'ms', this.abortOnFailure, this._stackTrace); - } else { - console.log('not yet', timeElapsed); - setTimeout(checkFrameLoaded, interval); - timeElapsed += interval; - } + timedRecheck = setTimeout(checkFrameLoaded, interval); + timeElapsed += interval; + console.log('waiting:', timeElapsed); } }); }; diff --git a/Website/test/e2e/pageObjects/content.js b/Website/test/e2e/pageObjects/content.js index b1087fcb83..4e20057a01 100644 --- a/Website/test/e2e/pageObjects/content.js +++ b/Website/test/e2e/pageObjects/content.js @@ -3,6 +3,9 @@ module.exports = { docktabs: { selector: 'dock[reference="main"] docktabs', commands: [{ + clickTab: function (index) { + this.click('docktab:nth-of-type(' + index + ')'); + }, closeTab: function (index) { this.click('docktab:nth-of-type(' + index + ') control[controltype="close"]'); } @@ -23,7 +26,8 @@ module.exports = { this.api.page.appWindow().prepare(); this .enter() - .waitForElementVisible('@browserFrame', 2000); + .waitForElementVisible('@browserFrame', 2000) + .waitForFrameLoad('@browserFrame', 1000); return this; } } diff --git a/Website/test/e2e/suite/content/editor.js b/Website/test/e2e/suite/content/editor.js index df91166f17..7c0485570d 100644 --- a/Website/test/e2e/suite/content/editor.js +++ b/Website/test/e2e/suite/content/editor.js @@ -1,11 +1,12 @@ module.exports = { - 'can open editor': function (browser) { + 'can edit front page': function (browser) { browser.url(browser.launchUrl + '/Composite/top.aspx'); var content = browser.page.content(); content .prepare() - // Select first tab - .section.docktabs.click('docktab:nth-of-type(1)'); + // Default situation: One open docktab showing content browser, with tab view. + // Select the one open tab + .section.docktabs.clickTab(1); content .enterFrame('@browserFrame') // Click edit button (identified by icon name) @@ -14,14 +15,17 @@ module.exports = { // Locate and check editor screen // relies on it being inside the second ui:view tag in existence in the content frame. .waitForElementPresent('view:nth-of-type(2) window', 1000) + .waitForFrameLoad('view:nth-of-type(2) window iframe', 1000) .enterFrame('view:nth-of-type(2) window iframe') .waitForElementVisible('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]', 1000) .waitForFrameLoad('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]', 1000); + // Enter frame with editor content content - // Find frame with editor content - .selectFrame('#tinymce > img:nth-child(1)') + .enterFrame('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]') + .enterFrame('iframe[src="tinymce.aspx?config=common"]') + .enterFrame('#editor_ifr') // Check that it has more than just one entry - .verify.visible('#tinymce > img:nth-child(2)') + .assert.visible('#tinymce > img:nth-child(2)') // Select the first element .click('#tinymce > img:nth-child(1)') // Click the toolbar button for properties @@ -67,13 +71,13 @@ module.exports = { .getAttribute('#savebutton', 'isdisabled', function (result) { browser.verify.equal('true', result.value); }); + // Close editor after you content - // Close editor after you .enter() .section.docktabs.closeTab(2); // Check that the change is made - browser - .pause(3000) + // browser + // .pause(3000) content .enterFrame('@browserFrame') .waitForElementVisible('#browsertabbox iframe', 1000) From c8ccebbafbf08066cd63903bb92bf74095445732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Wed, 29 Jun 2016 14:51:37 +0200 Subject: [PATCH 129/213] Finally! A working test that might actually be readable by humans to some extent. --- Website/test/e2e/commands/enterFrame.js | 8 ++- Website/test/e2e/commands/selectFrame.js | 5 +- Website/test/e2e/commands/waitForFrameLoad.js | 1 - Website/test/e2e/pageObjects/content.js | 8 +++ Website/test/e2e/pageObjects/editor.js | 29 ++++++++ Website/test/e2e/suite/content/editor.js | 71 ++++++++++--------- 6 files changed, 84 insertions(+), 38 deletions(-) create mode 100644 Website/test/e2e/pageObjects/editor.js diff --git a/Website/test/e2e/commands/enterFrame.js b/Website/test/e2e/commands/enterFrame.js index 4f752e382d..d4f2d2c661 100644 --- a/Website/test/e2e/commands/enterFrame.js +++ b/Website/test/e2e/commands/enterFrame.js @@ -9,9 +9,15 @@ util.inherits(EnterFrame, events.EventEmitter); EnterFrame.prototype.command = function(selector) { this.client.api.element('css selector', selector, result => { + if (!result.value.ELEMENT) { + this.client.assertion(false, null, null, 'Frame <' + selector + '> was not found', this.abortOnFailure, this._stackTrace); + } this.client.api.frame( result.value, - () => this.emit('complete') + () => { + // this.client.assertion(true, null, null, 'Entered frame <' + selector + '>', this.abortOnFailure); + this.emit('complete'); + } ); }); return this.client.api; diff --git a/Website/test/e2e/commands/selectFrame.js b/Website/test/e2e/commands/selectFrame.js index e629761912..ad14801c84 100644 --- a/Website/test/e2e/commands/selectFrame.js +++ b/Website/test/e2e/commands/selectFrame.js @@ -36,10 +36,11 @@ SelectFrame.prototype.command = function(selector, noReset) { result => { if (Array.isArray(result.value)) { result.value.forEach(key => this.client.api.frame(key)); - this.emit('complete'); + // this.client.assertion(true, null, null, 'Found element <' + selector + '> and entered frame containing it' + (noReset ? ' without resetting to top frame' : ''), this.abortOnFailure); } else { - throw new Error('Did not find selector "' + selector + '" in any frame.'); + this.client.assertion(false, null, null, 'Did not find selector <' + selector + '> in any frame.', this.abortOnFailure, this._stackTrace); } + this.emit('complete'); }); return this.client.api; }; diff --git a/Website/test/e2e/commands/waitForFrameLoad.js b/Website/test/e2e/commands/waitForFrameLoad.js index 0bd33ab827..bf57282c2c 100644 --- a/Website/test/e2e/commands/waitForFrameLoad.js +++ b/Website/test/e2e/commands/waitForFrameLoad.js @@ -39,7 +39,6 @@ WaitForFrameLoad.prototype.command = function(selector, timeout) { } else { timedRecheck = setTimeout(checkFrameLoaded, interval); timeElapsed += interval; - console.log('waiting:', timeElapsed); } }); }; diff --git a/Website/test/e2e/pageObjects/content.js b/Website/test/e2e/pageObjects/content.js index 4e20057a01..2d43918fa3 100644 --- a/Website/test/e2e/pageObjects/content.js +++ b/Website/test/e2e/pageObjects/content.js @@ -29,6 +29,14 @@ module.exports = { .waitForElementVisible('@browserFrame', 2000) .waitForFrameLoad('@browserFrame', 1000); return this; + }, + enterTabFrame: function (index) { + this + .enter() + .waitForElementPresent('view:nth-of-type(' + index + ') window', 1000) + .waitForFrameLoad('view:nth-of-type(' + index + ') window iframe', 1000) + .enterFrame('view:nth-of-type(' + index + ') window iframe') + return this; } } ] diff --git a/Website/test/e2e/pageObjects/editor.js b/Website/test/e2e/pageObjects/editor.js new file mode 100644 index 0000000000..755e4d5ef9 --- /dev/null +++ b/Website/test/e2e/pageObjects/editor.js @@ -0,0 +1,29 @@ +/** +* This page object requires an initial presence in a frame that contains a +* visual editor. Calling the enter command outside this context will fail, as +* there is no editor to enter. +*/ + +module.exports = { + elements: [ + { editorFrame: 'iframe[src^="/Composite/content/misc/editors/visualeditor/visualeditor.aspx"]' } + ], + commands: [ + { + enter: function () { + this + .waitForElementVisible('@editorFrame', 1000) + .waitForFrameLoad('@editorFrame', 1000) + .enterFrame('@editorFrame') + .enterFrame('iframe[src^="tinymce.aspx"]') + .enterFrame('#editor_ifr') + return this; + } + } + ], + sections: { + editorBody: { + selector: '#tinymce' + } + } +}; diff --git a/Website/test/e2e/suite/content/editor.js b/Website/test/e2e/suite/content/editor.js index 7c0485570d..c537df48c7 100644 --- a/Website/test/e2e/suite/content/editor.js +++ b/Website/test/e2e/suite/content/editor.js @@ -2,6 +2,7 @@ module.exports = { 'can edit front page': function (browser) { browser.url(browser.launchUrl + '/Composite/top.aspx'); var content = browser.page.content(); + var editor = browser.page.editor(); content .prepare() // Default situation: One open docktab showing content browser, with tab view. @@ -11,39 +12,39 @@ module.exports = { .enterFrame('@browserFrame') // Click edit button (identified by icon name) .click('toolbarbutton[image="page-edit-page"]') - .leaveFrame() // Locate and check editor screen - // relies on it being inside the second ui:view tag in existence in the content frame. - .waitForElementPresent('view:nth-of-type(2) window', 1000) - .waitForFrameLoad('view:nth-of-type(2) window iframe', 1000) - .enterFrame('view:nth-of-type(2) window iframe') - .waitForElementVisible('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]', 1000) - .waitForFrameLoad('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]', 1000); + // Relies on it being inside the second docktab frame in existence. + .enterTabFrame(2) // Enter frame with editor content - content - .enterFrame('iframe[src="/Composite/content/misc/editors/visualeditor/visualeditor.aspx?config=common"]') - .enterFrame('iframe[src="tinymce.aspx?config=common"]') - .enterFrame('#editor_ifr') + editor + .enter() // Check that it has more than just one entry - .assert.visible('#tinymce > img:nth-child(2)') + .section.editorBody + .assert.visible('img:nth-child(2)') // Select the first element - .click('#tinymce > img:nth-child(1)') + .click('img:nth-child(1)') // Click the toolbar button for properties + browser .selectFrame('toolbarbutton[cmd="compositeInsertRendering"]') .click('toolbarbutton[cmd="compositeInsertRendering"]') - .api.pause(1000); + .pause(1000); // Click the edit HTML button content .selectFrame('#renderingdialogpage') .click('htmldatadialog') - .api.pause(1000); - // Find and enter the editor in the dialog that just appeared - content + browser + .pause(1000) + // Find the editor in the dialog that just appeared .selectFrame('#masterdialogset') - .selectFrame('#visualeditor', true) - .selectFrame('body#tinymce', true); + .enterFrame('iframe[src^="/Composite/content/dialogs/wysiwygeditor/wysiwygeditordialog.aspx"]') + editor + .selectFrame('@editorFrame', true) + // Enter the editor frame + .enter(); // Edit its contents - browser.replaceContent('#tinymce > h1 > em', 'Jupiter'); + browser + .assert.elementPresent('#tinymce > h1 > em') + .replaceContent('#tinymce > h1 > em', 'Jupiter'); // Approve the change content .selectFrame('#masterdialogset') @@ -58,32 +59,34 @@ module.exports = { browser.pause(500) // Save change. content - .selectFrame('#savebutton'); - content - .getAttribute('#savebutton', 'isdisabled', function (result) { - browser.verify.equal(null, result.value); - }); - content + .selectFrame('#savebutton') + .verify.cssClassNotPresent('#savebutton', 'isdisabled') .click('#savebutton > labelbox'); browser .pause(1000) content - .getAttribute('#savebutton', 'isdisabled', function (result) { - browser.verify.equal('true', result.value); - }); - // Close editor after you - content - .enter() + .verify.cssClassPresent('#savebutton', 'isdisabled') + // Close editor after you + .enter() // Reset to content frame .section.docktabs.closeTab(2); // Check that the change is made - // browser - // .pause(3000) content .enterFrame('@browserFrame') .waitForElementVisible('#browsertabbox iframe', 1000) + .waitForFrameLoad('#browsertabbox iframe', 1000) .enterFrame('#browsertabbox iframe') // The below fails if page starts out with unpublished changes. .assert.containsText('div.jumbotron-content > h1 > em', 'Jupiter'); + }, + after: function (browser) { + var content = browser.page.content(); + // Revert changes + content + .enter() + .enterFrame('@browserFrame') + .click('#moreactionsbutton') + .click('menuitem[image="item-undo-unpublished-changes"]') + .click('toolbarbutton[image="item-publish"]') browser.end(); } }; From 2eebaf0e64fe14bd48c27427c8a7d1b68ac450a2 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Mon, 4 Jul 2016 12:53:33 +0200 Subject: [PATCH 130/213] Add container with named array - [BindingName: ControlName] --- .../TemplatedUiContainerBase.cs | 22 ++++++++++++++++++- Composite/RuntimeInformation.cs | 8 +++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs b/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs index 62aca196b5..2d819f9f35 100644 --- a/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs +++ b/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs @@ -7,7 +7,8 @@ using Composite.Core.ResourceSystem; using Composite.Core.WebClient.FlowMediators.FormFlowRendering; using Composite.Plugins.Forms.WebChannel.UiControlFactories; - +using System.Web.UI.HtmlControls; +using System.Linq; namespace Composite.Plugins.Forms.WebChannel.UiContainerFactories { @@ -86,6 +87,25 @@ protected override void OnPreRender(EventArgs e) } } + if (RuntimeInformation.IsTestEnvironment) { + + try + { + var mappings = new Dictionary(); + FormFlowUiDefinitionRenderer.ResolveBindingPathToCliendIDMappings(GetContainer(), mappings); + var control = new HtmlGenericControl("div"); + control.Attributes.Add("class", "qacontainer hide"); + foreach (var mapping in mappings) + { + control.Attributes.Add($"data-{mapping.Key}", mapping.Value.Replace('$', '_')); + } + GetFormPlaceHolder().Controls.Add(control); + } + catch { + //Nothing + } + } + base.OnPreRender(e); } diff --git a/Composite/RuntimeInformation.cs b/Composite/RuntimeInformation.cs index 0c714a4317..037ea6cad9 100644 --- a/Composite/RuntimeInformation.cs +++ b/Composite/RuntimeInformation.cs @@ -70,6 +70,14 @@ private static bool IsUnittestImpl } } + internal static bool IsTestEnvironment + { + get + { + return true; + } + } + /// From e93e927173e5db7ad3e7373f9250cfc75e995abf Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Mon, 4 Jul 2016 15:26:22 +0200 Subject: [PATCH 131/213] Add FormDefinitionName to form as qa attribute --- .../FormDefinitionFileMarkupProvider.cs | 10 +++++++++- .../Forms/ITestAutomationLocatorInformation.cs | 13 +++++++++++++ Composite/Composite.csproj | 1 + .../WebClient/FlowMediators/WebFlowUiMediator.cs | 14 +++++++++++++- 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 Composite/C1Console/Forms/ITestAutomationLocatorInformation.cs diff --git a/Composite/C1Console/Forms/DataServices/FormDefinitionFileMarkupProvider.cs b/Composite/C1Console/Forms/DataServices/FormDefinitionFileMarkupProvider.cs index 2b609409fd..744915c7d3 100644 --- a/Composite/C1Console/Forms/DataServices/FormDefinitionFileMarkupProvider.cs +++ b/Composite/C1Console/Forms/DataServices/FormDefinitionFileMarkupProvider.cs @@ -14,7 +14,7 @@ namespace Composite.C1Console.Forms.DataServices ///
/// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public class FormDefinitionFileMarkupProvider : IFormMarkupProvider + public class FormDefinitionFileMarkupProvider : IFormMarkupProvider, ITestAutomationLocatorInformation { private readonly string _formPath; @@ -27,6 +27,14 @@ public FormDefinitionFileMarkupProvider(string formPath) _formPath = formPath; } + /// + public string TestAutomationLocator + { + get + { + return Path.GetFileNameWithoutExtension(_formPath); + } + } /// public XmlReader GetReader() diff --git a/Composite/C1Console/Forms/ITestAutomationLocatorInformation.cs b/Composite/C1Console/Forms/ITestAutomationLocatorInformation.cs new file mode 100644 index 0000000000..22575d285a --- /dev/null +++ b/Composite/C1Console/Forms/ITestAutomationLocatorInformation.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Composite.C1Console.Forms +{ + public interface ITestAutomationLocatorInformation + { + string TestAutomationLocator { get; } + } +} diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 52f02f7d61..68d257db54 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -164,6 +164,7 @@ + diff --git a/Composite/Core/WebClient/FlowMediators/WebFlowUiMediator.cs b/Composite/Core/WebClient/FlowMediators/WebFlowUiMediator.cs index 0b2d57d7e2..cd75d99851 100644 --- a/Composite/Core/WebClient/FlowMediators/WebFlowUiMediator.cs +++ b/Composite/Core/WebClient/FlowMediators/WebFlowUiMediator.cs @@ -7,7 +7,8 @@ using Composite.C1Console.Forms.Flows; using Composite.C1Console.Forms.WebChannel; using Composite.Core.WebClient.FlowMediators.FormFlowRendering; - +using System.Linq; +using System.Web.UI.HtmlControls; namespace Composite.Core.WebClient.FlowMediators { @@ -43,6 +44,17 @@ public static Control GetFlowUi(FlowHandle flowHandle, string elementProviderNam webControl = webForm.BuildWebControl(); if (string.IsNullOrEmpty(webControl.ID)) webControl.ID = "FlowUI"; + + if (RuntimeInformation.IsTestEnvironment) + { + var testAutomationLocatorInformation = formFlowUiDefinition.MarkupProvider as ITestAutomationLocatorInformation; + if (testAutomationLocatorInformation != null) { + var htmlform = webControl.Controls.OfType().FirstOrDefault(); + if (htmlform != null) { + htmlform.Attributes.Add("data-qa", testAutomationLocatorInformation.TestAutomationLocator); + } + } + } } return webControl; From 7f3fb7dc89ef40d5a85a1734c3ff4195f500ff89 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Thu, 7 Jul 2016 14:32:40 -0400 Subject: [PATCH 132/213] add 'qa' attributes to the fields --- .../TemplatedUiContainerBase.cs | 6 +-- Website/Composite/CompileScripts.xml | 1 + .../scripts/source/top/ui/UserInterface.js | 3 +- .../fields/ResolverContainerBinding.js | 43 +++++++++++++++++++ Website/WebSite.csproj | 1 + 5 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 Website/Composite/scripts/source/top/ui/bindings/fields/ResolverContainerBinding.js diff --git a/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs b/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs index 2d819f9f35..3f3d184c0e 100644 --- a/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs +++ b/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs @@ -93,11 +93,11 @@ protected override void OnPreRender(EventArgs e) { var mappings = new Dictionary(); FormFlowUiDefinitionRenderer.ResolveBindingPathToCliendIDMappings(GetContainer(), mappings); - var control = new HtmlGenericControl("div"); - control.Attributes.Add("class", "qacontainer hide"); + var control = new HtmlGenericControl("ui:resolvercontainer"); + control.Attributes.Add("class", "resolvercontainer hide"); foreach (var mapping in mappings) { - control.Attributes.Add($"data-{mapping.Key}", mapping.Value.Replace('$', '_')); + control.Attributes.Add($"data-{mapping.Key}", mapping.Value); } GetFormPlaceHolder().Controls.Add(control); } diff --git a/Website/Composite/CompileScripts.xml b/Website/Composite/CompileScripts.xml index f980570ce8..7fd84c03d4 100644 --- a/Website/Composite/CompileScripts.xml +++ b/Website/Composite/CompileScripts.xml @@ -216,6 +216,7 @@ - + diff --git a/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js b/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js index ce442d9201..520c9e8e28 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/data/selectors/DataInputSelectorBinding.js @@ -423,7 +423,8 @@ DataInputSelectorBinding.prototype.select = function ( item, isDefault ) { this._selectedItemBinding = item; - this.setValue ( item.selectionValue ); + this.setValue(item.selectionValue); + this.validate(true); if ( !isDefault ) { this.dirty(); From 88471b44b8d4abf3c1b7f099a6d5510b79e383e7 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 2 Aug 2016 12:57:39 +0200 Subject: [PATCH 166/213] Showing version name in the bulk publishing view --- .../Composite/services/Tree/TreeServices.asmx | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/Website/Composite/services/Tree/TreeServices.asmx b/Website/Composite/services/Tree/TreeServices.asmx index d562d04e63..769b1fb519 100644 --- a/Website/Composite/services/Tree/TreeServices.asmx +++ b/Website/Composite/services/Tree/TreeServices.asmx @@ -89,47 +89,56 @@ namespace Composite.Services var rootChildren = ElementFacade.GetChildren(rootElements.ElementHandle, new SearchToken()); var allElements = GetPublishControlledDescendants(rootChildren); - var transitions = new Dictionary + var publicationStates = new Dictionary { {GenericPublishProcessController.Draft, StringResourceSystemFacade.GetString("Composite.Management", "PublishingStatus.draft")}, {GenericPublishProcessController.AwaitingApproval, StringResourceSystemFacade.GetString("Composite.Management", "PublishingStatus.awaitingApproval")}, {GenericPublishProcessController.AwaitingPublication, StringResourceSystemFacade.GetString("Composite.Management", "PublishingStatus.awaitingPublication")} }; - List actionRequiredPages = - (from element in allElements - where ((IPublishControlled)((DataEntityToken)element.ElementHandle.EntityToken).Data).PublicationStatus != "published" - select element).ToList(); + List> actionRequiredPages = + (from element in allElements + let publishControlledData = (IPublishControlled)((DataEntityToken)element.ElementHandle.EntityToken).Data + where publishControlledData.PublicationStatus != "published" + select new Tuple(element, publishControlledData)).ToList(); foreach (var actionRequiredPage in actionRequiredPages) { - var data = (IPublishControlled)((DataEntityToken) actionRequiredPage.ElementHandle.EntityToken).Data; + var propertyBag = actionRequiredPage.Item1.PropertyBag; + var data = actionRequiredPage.Item2; - actionRequiredPage.PropertyBag["Title"] = data.GetLabel(); + propertyBag["Title"] = data.GetLabel(); var publicationStatus = data.PublicationStatus; - actionRequiredPage.PropertyBag["Status"] = (transitions.ContainsKey(publicationStatus) - ? transitions[publicationStatus] - : "Unknown State"); + propertyBag["Status"] = publicationStates.ContainsKey(publicationStatus) + ? publicationStates[publicationStatus] + : "Unknown State"; + + var versionedData = data as IVersioned; + if (versionedData != null) + { + string versionName = versionedData.LocalizedVersionName(); // TODO: type cast? + if (!string.IsNullOrEmpty(versionName)) + { + propertyBag["Version"] = versionName; + } + } Func toSortableString = date => String.Format("{0:s}", date); var changeHistory = data as IChangeHistory; if (changeHistory != null) { - actionRequiredPage.PropertyBag["Modified"] = - changeHistory.ChangeDate.ToString(CultureInfo.CurrentCulture); - actionRequiredPage.PropertyBag["SortableModified"] = - toSortableString(changeHistory.ChangeDate); - actionRequiredPage.PropertyBag["ChangedBy"] = - changeHistory.ChangedBy; + propertyBag["Modified"] = changeHistory.ChangeDate.ToString(CultureInfo.CurrentCulture); + propertyBag["SortableModified"] = toSortableString(changeHistory.ChangeDate); + propertyBag["ChangedBy"] = changeHistory.ChangedBy; } var creationHistory = data as ICreationHistory; if (creationHistory != null) { - actionRequiredPage.PropertyBag["Created"] = + propertyBag["Created"] = creationHistory.CreationDate.ToString(CultureInfo.CurrentCulture); - actionRequiredPage.PropertyBag["SortableCreated"] = + propertyBag["SortableCreated"] = toSortableString(creationHistory.CreationDate); } @@ -141,8 +150,8 @@ namespace Composite.Services UserSettings.ActiveLocaleCultureInfo.Name); if (existingPagePublishSchedule != null) { - actionRequiredPage.PropertyBag["PublishDate"] = existingPagePublishSchedule.PublishDate.ToString(CultureInfo.CurrentCulture); - actionRequiredPage.PropertyBag["SortablePublishDate"] = toSortableString(existingPagePublishSchedule.PublishDate); + propertyBag["PublishDate"] = existingPagePublishSchedule.PublishDate.ToString(CultureInfo.CurrentCulture); + propertyBag["SortablePublishDate"] = toSortableString(existingPagePublishSchedule.PublishDate); } var existingPageUnpublishSchedule = PublishScheduleHelper.GetUnpublishSchedule(typeof(IPage), @@ -150,13 +159,13 @@ namespace Composite.Services UserSettings.ActiveLocaleCultureInfo.Name); if (existingPageUnpublishSchedule != null) { - actionRequiredPage.PropertyBag["UnpublishDate"] = existingPageUnpublishSchedule.UnpublishDate.ToString(CultureInfo.CurrentCulture); - actionRequiredPage.PropertyBag["SortableUnpublishDate"] = toSortableString(existingPageUnpublishSchedule.UnpublishDate); + propertyBag["UnpublishDate"] = existingPageUnpublishSchedule.UnpublishDate.ToString(CultureInfo.CurrentCulture); + propertyBag["SortableUnpublishDate"] = toSortableString(existingPageUnpublishSchedule.UnpublishDate); } } } - return actionRequiredPages.ToClientElementList(); + return actionRequiredPages.Select(pair => pair.Item1).ToList().ToClientElementList(); } IEnumerable GetPublishControlledDescendants(IEnumerable a) From aa4c053fe7b415ea1517d49b7fc8424d6454d3ad Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Tue, 2 Aug 2016 14:10:09 +0300 Subject: [PATCH 167/213] scroll for ViewUnpublishedItems --- .../ViewUnpublishedItems.aspx | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx b/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx index 9111a35859..cdf58bb054 100644 --- a/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx +++ b/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx @@ -15,7 +15,7 @@ " showglobaldata="<%=Request["showglobaldata"]%>"> @@ -23,31 +23,33 @@ - - - - - - - - - - - - - - - - -
- - - - - - - -
+ + + + + + + + + + + + + + + + + +
+ + + + + + + +
+
From 3be2b85645ff0fc087bc2a50cd51cb7088db85a7 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 2 Aug 2016 14:06:05 +0200 Subject: [PATCH 168/213] Bulk publishing view - fixing rows duplication when element bundling is used --- .../Composite/services/Tree/TreeServices.asmx | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/Website/Composite/services/Tree/TreeServices.asmx b/Website/Composite/services/Tree/TreeServices.asmx index 769b1fb519..6a8a4cf5ed 100644 --- a/Website/Composite/services/Tree/TreeServices.asmx +++ b/Website/Composite/services/Tree/TreeServices.asmx @@ -85,9 +85,8 @@ namespace Composite.Services [WebMethod] public List GetUnpublishedElements(string dummy) { - var rootElements = ElementFacade.GetPerspectiveElements(false).First(); - var rootChildren = ElementFacade.GetChildren(rootElements.ElementHandle, new SearchToken()); - var allElements = GetPublishControlledDescendants(rootChildren); + var rootElement = ElementFacade.GetPerspectiveElements(false).First(); + var allElements = GetPublishControlledDescendants(rootElement.ElementHandle); var publicationStates = new Dictionary { @@ -168,25 +167,36 @@ namespace Composite.Services return actionRequiredPages.Select(pair => pair.Item1).ToList().ToClientElementList(); } - IEnumerable GetPublishControlledDescendants(IEnumerable a) - { - foreach (var element in a) - { - var temp = ElementFacade.GetChildren(element.ElementHandle, new SearchToken()); - if (temp != null) - { - foreach (var element2 in GetPublishControlledDescendants(temp)) - { - yield return element2; - } - - } - if (IsPublishControlled(element)) - { - yield return element; - } - } - } + IEnumerable GetPublishControlledDescendants(ElementHandle elementHandle) + { + HashSet elementBundles = null; + + var children = ElementFacade.GetChildren(elementHandle, new SearchToken()) ?? Enumerable.Empty(); + foreach (var child in children) + { + if (IsPublishControlled(child)) + { + yield return child; + } + + string elementBundle = child.VisualData.ElementBundle; + if (elementBundle != null) + { + elementBundles = elementBundles ?? new HashSet(); + if (elementBundles.Contains(elementBundle)) + { + continue; + } + + elementBundles.Add(elementBundle); + } + + foreach (var element in GetPublishControlledDescendants(child.ElementHandle)) + { + yield return element; + } + } + } private bool IsPublishControlled(Element v) { From d3a3cb56f34c88c0cb50b9d29f004df619f291c7 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 3 Aug 2016 11:54:23 +0200 Subject: [PATCH 169/213] Minor localization strings fix --- .../localization/Composite.C1Console.Users.en-us.xml | 2 +- Website/Composite/localization/Composite.Management.en-us.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Website/Composite/localization/Composite.C1Console.Users.en-us.xml b/Website/Composite/localization/Composite.C1Console.Users.en-us.xml index 8b8ed2ca5c..571740803e 100644 --- a/Website/Composite/localization/Composite.C1Console.Users.en-us.xml +++ b/Website/Composite/localization/Composite.C1Console.Users.en-us.xml @@ -26,7 +26,7 @@ - + diff --git a/Website/Composite/localization/Composite.Management.en-us.xml b/Website/Composite/localization/Composite.Management.en-us.xml index a2c3d57e7f..11c80e1683 100644 --- a/Website/Composite/localization/Composite.Management.en-us.xml +++ b/Website/Composite/localization/Composite.Management.en-us.xml @@ -42,7 +42,7 @@ - + @@ -95,7 +95,7 @@ - + From 11eb01d9a7ef33cec957833db220c45732ae9c58 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Wed, 3 Aug 2016 13:57:07 +0300 Subject: [PATCH 170/213] fix selecting valid version for entitytoken in tree --- .../views/browser/BrowserPageBinding.js | 15 +++-------- .../bindings/UnpublishedPageBinding.js | 1 - .../scripts/source/top/system/SystemNode.js | 27 ++++++++++++++++--- .../top/ui/bindings/stage/StageBinding.js | 4 +++ .../ui/bindings/system/SystemTreeBinding.js | 26 +++++++++++++----- .../bindings/system/SystemTreeNodeBinding.js | 12 +++++++++ 6 files changed, 64 insertions(+), 21 deletions(-) diff --git a/Website/Composite/content/views/browser/BrowserPageBinding.js b/Website/Composite/content/views/browser/BrowserPageBinding.js index a9c0b98e85..b1e42d9198 100644 --- a/Website/Composite/content/views/browser/BrowserPageBinding.js +++ b/Website/Composite/content/views/browser/BrowserPageBinding.js @@ -148,7 +148,7 @@ BrowserPageBinding.prototype.handleBroadcast = function (broadcast, arg) { if (treenode.node.isMultiple()) { var list = new List(); treenode.node.getDatas().each(function(data) { - list.add(new SelectorBindingSelection(data.BundleElementName ? data.BundleElementName : data.Label, data.ElementKey, data.ElementKey === treenode.node.getHandle())); + list.add(new SelectorBindingSelection(data.BundleElementName ? data.BundleElementName : data.Label, data.EntityToken, data.EntityToken === treenode.node.getEntityToken())); }); bundleselector.populateFromList(list); bundleselector.show(); @@ -1251,16 +1251,9 @@ BrowserPageBinding.prototype.getBundleSelector = function () { switch (action.type) { case SelectorBinding.ACTION_SELECTIONCHANGED: - var bundleValue = binding.getValue(); - var treenode = this.getSystemTree().getFocusedTreeNodeBindings().getFirst(); - if (treenode) { - var selectedBundleNode = treenode.node; - treenode.node.select(bundleValue); - treenode.isDisabled = treenode.node.isDisabled(); - treenode.setLabel(treenode.node.getLabel()); - treenode.setToolTip(treenode.node.getToolTip()); - treenode.setImage(treenode.computeImage()); - this.getSystemTree().focusSingleTreeNodeBinding(treenode); + if (selector === binding) { + var entityToken = binding.getValue(); + this.getSystemTree()._focusTreeNodeByEntityToken(entityToken); } break; } diff --git a/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js b/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js index fe1e4b630e..27736a2df9 100644 --- a/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js +++ b/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js @@ -383,7 +383,6 @@ UnpublishedPageBinding.prototype.handleBroadcast = function (broadcast, arg) { } else { this.isSelectedTab = false; } - console.log(arg); break; case BroadcastMessages.SYSTEMTREEBINDING_REFRESH: diff --git a/Website/Composite/scripts/source/top/system/SystemNode.js b/Website/Composite/scripts/source/top/system/SystemNode.js index 9396cef7c6..d031c1b76c 100644 --- a/Website/Composite/scripts/source/top/system/SystemNode.js +++ b/Website/Composite/scripts/source/top/system/SystemNode.js @@ -395,15 +395,36 @@ SystemNode.prototype.getDatas = function () { * @return {void} */ SystemNode.prototype.select = function (handle) { - + + if (this._datas != null) { + this._datas.each(function (data) { + if (data.ElementKey === handle) { + this._data = data; + this._actionProfile = null; + this._registerSystemActions(); + return false; + } + return true; + }, this); + } +} + + +/** + * @param {string} entityToken + * @return {void} + */ +SystemNode.prototype.selectByToken = function (entityToken) { + if (this._datas != null) { this._datas.each(function (data) { - if (data.ElementKey == handle) { + if (data.EntityToken === entityToken) { this._data = data; this._actionProfile = null; this._registerSystemActions(); return false; } + return true; }, this); } } @@ -419,7 +440,7 @@ SystemNode.prototype.getEntityTokens = function () { result.add(data.EntityToken); }); } else { - result.add(this._data.EntityToken) + result.add(this._data.EntityToken); } return result; } diff --git a/Website/Composite/scripts/source/top/ui/bindings/stage/StageBinding.js b/Website/Composite/scripts/source/top/ui/bindings/stage/StageBinding.js index 1b5149fbcd..4308adb636 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/stage/StageBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/stage/StageBinding.js @@ -956,6 +956,10 @@ StageBinding.prototype.selectBrowserTab = function () { var deck = this._decksBinding.getSelectedDeckBinding(); var browserTab = deck.getBrowserTab(); if (browserTab && !browserTab.isSelected) { + var tree = deck.getSystemTree(); + if (tree != null) { + tree.setHandleToken(null); + } browserTab.containingTabBoxBinding.select(browserTab, true); } } diff --git a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js index 9600590cb1..0cd764360b 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js @@ -613,11 +613,13 @@ SystemTreeBinding.prototype._handleDockTabSelect = function (tab) { * find a matching treenode. */ if (isVisible) { + if (tab.isExplorerTab) { - var self = this, token = this._handleToken; - this._handleToken = null; + var token = this.getHandleToken(); + var self = this; + this.setHandleToken(null); var selectedTreeNode = this.getFocusedTreeNodeBindings().getFirst(); - if (selectedTreeNode && selectedTreeNode.node.getEntityToken() == token) { + if (selectedTreeNode && selectedTreeNode.node.getEntityToken() === token) { EventBroadcaster.broadcast( BroadcastMessages.SYSTEMTREENODEBINDING_FOCUS, selectedTreeNode @@ -631,13 +633,23 @@ SystemTreeBinding.prototype._handleDockTabSelect = function (tab) { } } else { - var self = this, token = tab.getEntityToken(); - this._handleToken = token; + this.setHandleToken(tab.getEntityToken()); } } } +SystemTreeBinding.prototype.setHandleToken = function (token) { + + this._handleToken = token; +} + +SystemTreeBinding.prototype.getHandleToken = function () { + + return this._handleToken; +} + + /** * Focus the first encountered treenode with a given entityToken * in support of the lock-tree-to-editor feature. @@ -667,7 +679,9 @@ SystemTreeBinding.prototype._focusTreeNodeByEntityToken = function (entityToken, return result; }); if (treenode != null) { - if (!treenode.isFocused) { + + if (!treenode.isFocused || treenode.node.getEntityToken() !== entityToken) { + treenode.selectToken(entityToken); this.focusSingleTreeNodeBinding(treenode, true); } else { treenode.dispatchAction(TreeNodeBinding.ACTION_FOCUSED); // to reveal it! diff --git a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js index 327627f8e2..b62520aa22 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js @@ -615,6 +615,18 @@ SystemTreeNodeBinding.prototype.hasChildren = function () { return this.node.hasChildren (); }; +/** + * @param {string} entityToken + */ +SystemTreeNodeBinding.prototype.selectToken = function (entityToken) { + + this.node.selectByToken(entityToken); + this.isDisabled = this.node.isDisabled(); + this.setLabel(this.node.getLabel()); + this.setToolTip(this.node.getToolTip()); + this.setImage(this.computeImage()); +} + /** * SystemTreeNodeBinding factory. Notice that we supply a {@link SystemNode} as argument here! * @param {SystemNode} node From b6e1a060dee8e07997170cd172bce4ac4ddc9b8f Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Wed, 3 Aug 2016 14:17:35 +0300 Subject: [PATCH 171/213] Global tagvalue for bulk publish commands --- .../GenericPublishProcessController.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs b/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs index 9338561233..7c0d440270 100644 --- a/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs +++ b/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs @@ -45,7 +45,7 @@ public sealed class GenericPublishProcessController : IPublishProcessController /// public const string SentToDraft = "sentToDraft"; - private static readonly string _bulkPublishingCommands = "BulkPublishingCommands"; + public static string BulkPublishingCommandsTag { get; } = "BulkPublishingCommands"; private static readonly string _backToAwaitingApproval = "awaitingApprovalBack"; private static readonly string _forwardToAwaitingApproval = "awaitingApprovalForward"; @@ -139,7 +139,7 @@ public GenericPublishProcessController() ActionGroup = WorkflowActionGroup } }, - TagValue = _bulkPublishingCommands + TagValue = BulkPublishingCommandsTag }; @@ -159,7 +159,7 @@ public GenericPublishProcessController() ActionGroup = WorkflowActionGroup } }, - TagValue = _bulkPublishingCommands + TagValue = BulkPublishingCommandsTag }; @@ -179,7 +179,7 @@ public GenericPublishProcessController() ActionGroup = WorkflowActionGroup } }, - TagValue = _bulkPublishingCommands + TagValue = BulkPublishingCommandsTag }; @@ -205,7 +205,7 @@ public GenericPublishProcessController() ActionBundle = "Publish" } }, - TagValue = _bulkPublishingCommands + TagValue = BulkPublishingCommandsTag }; @@ -226,7 +226,7 @@ public GenericPublishProcessController() ActionGroup = WorkflowActionGroup } }, - TagValue = _bulkPublishingCommands + TagValue = BulkPublishingCommandsTag }; @@ -246,7 +246,7 @@ public GenericPublishProcessController() ActionGroup = WorkflowActionGroup } }, - TagValue = _bulkPublishingCommands, + TagValue = BulkPublishingCommandsTag, }; @@ -267,7 +267,7 @@ public GenericPublishProcessController() ActionGroup = WorkflowActionGroup } }, - TagValue = _bulkPublishingCommands + TagValue = BulkPublishingCommandsTag }; @@ -287,7 +287,7 @@ public GenericPublishProcessController() ActionGroup = WorkflowActionGroup } }, - TagValue = _bulkPublishingCommands + TagValue = BulkPublishingCommandsTag }; @@ -307,7 +307,7 @@ public GenericPublishProcessController() ActionGroup = WorkflowActionGroup } }, - TagValue = _bulkPublishingCommands + TagValue = BulkPublishingCommandsTag }; @@ -328,7 +328,7 @@ public GenericPublishProcessController() ActionGroup = WorkflowActionGroup } }, - TagValue = _bulkPublishingCommands + TagValue = BulkPublishingCommandsTag }; _visualTransitionsActions = new Dictionary> From a70fdbec712025afb57a4f054afabbd682cde925 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Thu, 4 Aug 2016 11:05:57 +0300 Subject: [PATCH 172/213] display dates in timezone format --- Composite/Composite.csproj | 1 + .../Core/Extensions/DateExtensionMethods.cs | 76 +++++++++++++++++++ .../DateTimeSelectors/DateSelector.ascx.cs | 39 +++++----- .../Composite/services/Tree/TreeServices.asmx | 9 ++- 4 files changed, 99 insertions(+), 26 deletions(-) create mode 100644 Composite/Core/Extensions/DateExtensionMethods.cs diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index b8b03d1604..eba61f2621 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -173,6 +173,7 @@ + diff --git a/Composite/Core/Extensions/DateExtensionMethods.cs b/Composite/Core/Extensions/DateExtensionMethods.cs new file mode 100644 index 0000000000..d48b888b67 --- /dev/null +++ b/Composite/Core/Extensions/DateExtensionMethods.cs @@ -0,0 +1,76 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using Composite.Core.Configuration; +using Composite.Core.ResourceSystem; +using JetBrains.Annotations; + + +namespace Composite.Core.Extensions +{ + /// + /// + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public static class DateExtensionMethods + { + + /// + private static string TimeZoneAbbriviatedName() + { + return StringResourceSystemFacade.GetString("Composite.Plugins.TimezoneAbbriviations", + "TimezoneAbbriviations." + GlobalSettingsFacade.TimeZone.Id); + } + + + /// + public static string ToTimeZoneDateTimeString(this DateTime? dateTime) + { + return dateTime?.ToTimeZoneDateTimeString(); + } + + /// + public static string ToTimeZoneDateTimeString(this DateTime dateTime) + { + + var convertedToShow = dateTime.ToTimeZone(); + + return + $"{convertedToShow.ToShortDateString()} {convertedToShow.ToShortTimeString()} {TimeZoneAbbriviatedName()}"; + } + + /// + public static string ToTimeZoneDateString(this DateTime? dateTime) + { + return dateTime?.ToTimeZoneDateString(); + } + + /// + public static string ToTimeZoneDateString(this DateTime dateTime) + { + var convertedToShow = dateTime.ToTimeZone(); + + return convertedToShow.ToShortDateString(); + } + + /// + public static DateTime FromTimeZoneToUtc(this DateTime dateTime) + { + return TimeZoneInfo.ConvertTime(dateTime, GlobalSettingsFacade.TimeZone, TimeZoneInfo.Utc); + } + + /// + public static DateTime ToTimeZone(this DateTime dateTime) + { + return TimeZoneInfo.ConvertTime(dateTime, GlobalSettingsFacade.TimeZone); + } + + /// + public static bool TryParseInTimeZone(string s, out DateTime result) + { + return DateTime.TryParse(s.Replace(TimeZoneAbbriviatedName(), ""), out result); + } + + } +} diff --git a/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx.cs b/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx.cs index 3d2f9fe41e..6890270136 100644 --- a/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx.cs +++ b/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx.cs @@ -7,6 +7,8 @@ using System.Web.UI; using System.Linq; using Composite.Core.Configuration; +using Composite.Core.Extensions; + namespace Composite.controls.FormsControls.FormUiControlTemplates.DateTimeSelectors { @@ -23,12 +25,6 @@ private string ResetOnClickValue public PlaceHolder MessagesPlaceHolder; public string CurrentStringValue; - private static string TimeZoneAbbriviatedName() - { - return StringResourceSystemFacade.GetString("Composite.Plugins.TimezoneAbbriviations", - "TimezoneAbbriviations." + GlobalSettingsFacade.TimeZone.Id); - } - protected void Page_Init(object sender, EventArgs e) { if (ReadOnly) @@ -46,16 +42,13 @@ private void InsertSelectedDate(DateTime? toShow) { if (toShow.HasValue && toShow.Value != DateTime.MinValue) { - DateTime convertedToShow = TimeZoneInfo.ConvertTime(toShow.Value, GlobalSettingsFacade.TimeZone); - if (!ShowHours) { - this.CurrentStringValue = convertedToShow.ToShortDateString(); + this.CurrentStringValue = toShow.Value.ToTimeZoneDateString(); } else { - this.CurrentStringValue = string.Format("{0} {1} {2}", convertedToShow.ToShortDateString(), - convertedToShow.ToShortTimeString(), TimeZoneAbbriviatedName()); + this.CurrentStringValue = toShow.Value.ToTimeZoneDateTimeString(); } } else @@ -77,14 +70,16 @@ public override void BindStateToProperties() } else { - string stringValueWithoutTimezone = this.CurrentStringValue.Replace(TimeZoneAbbriviatedName(),""); - - DateTime parsedTime = DateTime.Parse(stringValueWithoutTimezone); + DateTime parsedTime; + if (!DateExtensionMethods.TryParseInTimeZone(this.CurrentStringValue, out parsedTime)) + { + throw new FormatException(); + } if (!ShowHours) parsedTime -= parsedTime.TimeOfDay; - this.Date = TimeZoneInfo.ConvertTime(parsedTime, GlobalSettingsFacade.TimeZone,TimeZoneInfo.Utc).ToLocalTime(); + this.Date = parsedTime.FromTimeZoneToUtc().ToLocalTime(); } this.IsValid = true; } @@ -98,16 +93,16 @@ public override void BindStateToProperties() private string SampleDateString { - get - { + get + { if(ShowHours) { - return string.Format("{0} {1} {2}", DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString(),TimeZoneAbbriviatedName()); + return DateTime.Now.ToTimeZoneDateTimeString(); } else { - return DateTime.Now.ToShortDateString(); - } + return DateTime.Now.ToTimeZoneDateString(); + } } } @@ -159,11 +154,11 @@ public void CalendarSelectionChange(object sender, EventArgs e) if (ShowHours) { DateTime oldDateTime; - if (DateTime.TryParse(this.CurrentStringValue.Replace(TimeZoneAbbriviatedName(), ""), out oldDateTime)) + if (DateExtensionMethods.TryParseInTimeZone(this.CurrentStringValue, out oldDateTime)) toShow += oldDateTime.TimeOfDay; } - InsertSelectedDate(TimeZoneInfo.ConvertTime(toShow, GlobalSettingsFacade.TimeZone, TimeZoneInfo.Utc)); + InsertSelectedDate(toShow.FromTimeZoneToUtc()); this.MessagesPlaceHolder.Controls.Add(new DocumentDirtyEvent()); } diff --git a/Website/Composite/services/Tree/TreeServices.asmx b/Website/Composite/services/Tree/TreeServices.asmx index 6a8a4cf5ed..8619a9f2e5 100644 --- a/Website/Composite/services/Tree/TreeServices.asmx +++ b/Website/Composite/services/Tree/TreeServices.asmx @@ -27,6 +27,7 @@ using Composite.Data.ProcessControlled; using Composite.Data.ProcessControlled.ProcessControllers.GenericPublishProcessController; using Composite.Data.PublishScheduling; using Composite.Data.Types; +using Composite.Core.Extensions; // Search token stuff using Composite.Plugins.Elements.ElementProviders.MediaFileProviderElementProvider; @@ -128,7 +129,7 @@ namespace Composite.Services var changeHistory = data as IChangeHistory; if (changeHistory != null) { - propertyBag["Modified"] = changeHistory.ChangeDate.ToString(CultureInfo.CurrentCulture); + propertyBag["Modified"] = changeHistory.ChangeDate.ToTimeZoneDateTimeString(); propertyBag["SortableModified"] = toSortableString(changeHistory.ChangeDate); propertyBag["ChangedBy"] = changeHistory.ChangedBy; } @@ -136,7 +137,7 @@ namespace Composite.Services if (creationHistory != null) { propertyBag["Created"] = - creationHistory.CreationDate.ToString(CultureInfo.CurrentCulture); + creationHistory.CreationDate.ToTimeZoneDateTimeString(); propertyBag["SortableCreated"] = toSortableString(creationHistory.CreationDate); } @@ -149,7 +150,7 @@ namespace Composite.Services UserSettings.ActiveLocaleCultureInfo.Name); if (existingPagePublishSchedule != null) { - propertyBag["PublishDate"] = existingPagePublishSchedule.PublishDate.ToString(CultureInfo.CurrentCulture); + propertyBag["PublishDate"] = existingPagePublishSchedule.PublishDate.ToTimeZoneDateTimeString(); propertyBag["SortablePublishDate"] = toSortableString(existingPagePublishSchedule.PublishDate); } @@ -158,7 +159,7 @@ namespace Composite.Services UserSettings.ActiveLocaleCultureInfo.Name); if (existingPageUnpublishSchedule != null) { - propertyBag["UnpublishDate"] = existingPageUnpublishSchedule.UnpublishDate.ToString(CultureInfo.CurrentCulture); + propertyBag["UnpublishDate"] = existingPageUnpublishSchedule.UnpublishDate.ToTimeZoneDateTimeString(); propertyBag["SortableUnpublishDate"] = toSortableString(existingPageUnpublishSchedule.UnpublishDate); } From 980fabe69d4bcd4e1fa2a845af44e2d11aba820c Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 4 Aug 2016 11:30:11 +0200 Subject: [PATCH 173/213] Fixing a typo in a localization file name --- Composite/Composite.csproj | 2 +- ...Methods.cs => DateTimeExtensionMethods.cs} | 16 +-- .../DateTimeSelectors/DateSelector.ascx.cs | 4 +- ...te.Plugins.TimezoneAbbreviations.en-us.xml | 131 ++++++++++++++++++ ...te.Plugins.TimezoneAbbriviations.en-us.xml | 131 ------------------ Website/WebSite.csproj | 2 +- 6 files changed, 141 insertions(+), 145 deletions(-) rename Composite/Core/Extensions/{DateExtensionMethods.cs => DateTimeExtensionMethods.cs} (81%) create mode 100644 Website/Composite/localization/Composite.Plugins.TimezoneAbbreviations.en-us.xml delete mode 100644 Website/Composite/localization/Composite.Plugins.TimezoneAbbriviations.en-us.xml diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index eba61f2621..d4404a6766 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -173,7 +173,7 @@ - + diff --git a/Composite/Core/Extensions/DateExtensionMethods.cs b/Composite/Core/Extensions/DateTimeExtensionMethods.cs similarity index 81% rename from Composite/Core/Extensions/DateExtensionMethods.cs rename to Composite/Core/Extensions/DateTimeExtensionMethods.cs index d48b888b67..6227af2f06 100644 --- a/Composite/Core/Extensions/DateExtensionMethods.cs +++ b/Composite/Core/Extensions/DateTimeExtensionMethods.cs @@ -1,10 +1,6 @@ using System; -using System.Globalization; -using System.IO; -using System.Text; using Composite.Core.Configuration; using Composite.Core.ResourceSystem; -using JetBrains.Annotations; namespace Composite.Core.Extensions @@ -13,14 +9,14 @@ namespace Composite.Core.Extensions ///
/// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public static class DateExtensionMethods + public static class DateTimeExtensionMethods { /// - private static string TimeZoneAbbriviatedName() + private static string TimeZoneAbbreviatedName() { - return StringResourceSystemFacade.GetString("Composite.Plugins.TimezoneAbbriviations", - "TimezoneAbbriviations." + GlobalSettingsFacade.TimeZone.Id); + return StringResourceSystemFacade.GetString("Composite.Plugins.TimezoneAbbreviations", + "TimezoneAbbreviations." + GlobalSettingsFacade.TimeZone.Id); } @@ -37,7 +33,7 @@ public static string ToTimeZoneDateTimeString(this DateTime dateTime) var convertedToShow = dateTime.ToTimeZone(); return - $"{convertedToShow.ToShortDateString()} {convertedToShow.ToShortTimeString()} {TimeZoneAbbriviatedName()}"; + $"{convertedToShow.ToShortDateString()} {convertedToShow.ToShortTimeString()} {TimeZoneAbbreviatedName()}"; } /// @@ -69,7 +65,7 @@ public static DateTime ToTimeZone(this DateTime dateTime) /// public static bool TryParseInTimeZone(string s, out DateTime result) { - return DateTime.TryParse(s.Replace(TimeZoneAbbriviatedName(), ""), out result); + return DateTime.TryParse(s.Replace(TimeZoneAbbreviatedName(), ""), out result); } } diff --git a/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx.cs b/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx.cs index 6890270136..4652ddad2b 100644 --- a/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx.cs +++ b/Website/Composite/controls/FormsControls/FormUiControlTemplates/DateTimeSelectors/DateSelector.ascx.cs @@ -71,7 +71,7 @@ public override void BindStateToProperties() else { DateTime parsedTime; - if (!DateExtensionMethods.TryParseInTimeZone(this.CurrentStringValue, out parsedTime)) + if (!DateTimeExtensionMethods.TryParseInTimeZone(this.CurrentStringValue, out parsedTime)) { throw new FormatException(); } @@ -154,7 +154,7 @@ public void CalendarSelectionChange(object sender, EventArgs e) if (ShowHours) { DateTime oldDateTime; - if (DateExtensionMethods.TryParseInTimeZone(this.CurrentStringValue, out oldDateTime)) + if (DateTimeExtensionMethods.TryParseInTimeZone(this.CurrentStringValue, out oldDateTime)) toShow += oldDateTime.TimeOfDay; } diff --git a/Website/Composite/localization/Composite.Plugins.TimezoneAbbreviations.en-us.xml b/Website/Composite/localization/Composite.Plugins.TimezoneAbbreviations.en-us.xml new file mode 100644 index 0000000000..0fca1c398b --- /dev/null +++ b/Website/Composite/localization/Composite.Plugins.TimezoneAbbreviations.en-us.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Website/Composite/localization/Composite.Plugins.TimezoneAbbriviations.en-us.xml b/Website/Composite/localization/Composite.Plugins.TimezoneAbbriviations.en-us.xml deleted file mode 100644 index b4491ed95e..0000000000 --- a/Website/Composite/localization/Composite.Plugins.TimezoneAbbriviations.en-us.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Website/WebSite.csproj b/Website/WebSite.csproj index 297f80f074..f2d5d8a7db 100644 --- a/Website/WebSite.csproj +++ b/Website/WebSite.csproj @@ -560,7 +560,7 @@ - + From 0f4d8656c945c022b48290dbc400e07fe4f74a33 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 4 Aug 2016 13:45:58 +0200 Subject: [PATCH 174/213] List of unpublished pages - showing publish/unpublish dates --- Composite/Data/Types/IVersionedDataHelper.cs | 42 ++++++++++--- .../Composite/services/Tree/TreeServices.asmx | 60 ++++++++++++------- 2 files changed, 75 insertions(+), 27 deletions(-) diff --git a/Composite/Data/Types/IVersionedDataHelper.cs b/Composite/Data/Types/IVersionedDataHelper.cs index 57736b4dca..0acb4b03fe 100644 --- a/Composite/Data/Types/IVersionedDataHelper.cs +++ b/Composite/Data/Types/IVersionedDataHelper.cs @@ -9,25 +9,53 @@ namespace Composite.Data.Types ///
public abstract class VersionedPageHelperContract { - public abstract string LocalizedVersionName(T str) where T : IVersioned; + /// + public abstract string LocalizedVersionName(T data) where T : IVersioned; + + /// + public abstract DateTime? GetPublishDate(T data) where T : IVersioned; + + /// + public abstract DateTime? GetUnpublishDate(T data) where T : IVersioned; } + /// public static class VersionedPageHelper { - private static List instances; + private static List _instances; + /// public static void RegisterVersionHelper(VersionedPageHelperContract vpc) { - if(instances==null) - instances = new List(); - instances.Add(vpc); + if (_instances == null) + { + _instances = new List(); + } + + _instances.Add(vpc); } + /// public static string LocalizedVersionName(this T str) where T : IVersioned { - if (instances == null) + if (_instances == null) + { return ""; - return instances.Select(p=>p.LocalizedVersionName(str)).Single(p=>p != null); + } + + return _instances.Select(p => p.LocalizedVersionName(str)).Single(name => name != null); + } + + /// + public static DateTime? GetPublishDate(this IVersioned data) + { + return _instances?.Select(p => p.GetPublishDate(data)).FirstOrDefault(date => date != null); + } + + /// + public static DateTime? GetUnpublishDate(this IVersioned data) + { + return _instances?.Select(p => p.GetUnpublishDate(data)).FirstOrDefault(date => date != null); } } } diff --git a/Website/Composite/services/Tree/TreeServices.asmx b/Website/Composite/services/Tree/TreeServices.asmx index 8619a9f2e5..cf6bbc18d6 100644 --- a/Website/Composite/services/Tree/TreeServices.asmx +++ b/Website/Composite/services/Tree/TreeServices.asmx @@ -142,32 +142,52 @@ namespace Composite.Services toSortableString(creationHistory.CreationDate); } - var selectedPage = data as IPage; - if (selectedPage != null) - { - var existingPagePublishSchedule = PublishScheduleHelper.GetPublishSchedule(typeof (IPage), - selectedPage.Id.ToString(), - UserSettings.ActiveLocaleCultureInfo.Name); - if (existingPagePublishSchedule != null) - { - propertyBag["PublishDate"] = existingPagePublishSchedule.PublishDate.ToTimeZoneDateTimeString(); - propertyBag["SortablePublishDate"] = toSortableString(existingPagePublishSchedule.PublishDate); - } + DateTime? publishDate, unpublishDate; + GetPublicationDates(data, out publishDate, out unpublishDate); - var existingPageUnpublishSchedule = PublishScheduleHelper.GetUnpublishSchedule(typeof(IPage), - selectedPage.Id.ToString(), - UserSettings.ActiveLocaleCultureInfo.Name); - if (existingPageUnpublishSchedule != null) - { - propertyBag["UnpublishDate"] = existingPageUnpublishSchedule.UnpublishDate.ToTimeZoneDateTimeString(); - propertyBag["SortableUnpublishDate"] = toSortableString(existingPageUnpublishSchedule.UnpublishDate); - } + if (publishDate != null) + { + propertyBag["PublishDate"] = publishDate.ToTimeZoneDateTimeString(); + } + propertyBag["SortablePublishDate"] = toSortableString(publishDate ?? DateTime.MinValue); + - } + if (unpublishDate != null) + { + propertyBag["UnpublishDate"] = unpublishDate.ToTimeZoneDateTimeString(); + } + propertyBag["SortableUnpublishDate"] = toSortableString(unpublishDate ?? DateTime.MinValue); } return actionRequiredPages.Select(pair => pair.Item1).ToList().ToClientElementList(); } + private void GetPublicationDates(IData data, out DateTime? publicationDate, out DateTime? unpublicationDate) + { + var versionedData = data as IVersioned; + + if (versionedData != null) + { + publicationDate = versionedData.GetPublishDate(); + unpublicationDate = versionedData.GetUnpublishDate(); + } + else + { + var interfaceType = data.DataSourceId.InterfaceType; + string key = data.GetUniqueKey().ToString(); + string cultureName = UserSettings.ActiveLocaleCultureInfo.Name; + + var publishSchedule = PublishScheduleHelper.GetPublishSchedule( + interfaceType, key, cultureName); + + publicationDate = publishSchedule != null ? (DateTime?)publishSchedule.PublishDate : null; + + var unpublishSchedule = PublishScheduleHelper.GetUnpublishSchedule( + interfaceType, key, cultureName); + + unpublicationDate = unpublishSchedule != null ? (DateTime?)unpublishSchedule.UnpublishDate : null; + } + } + IEnumerable GetPublishControlledDescendants(ElementHandle elementHandle) { HashSet elementBundles = null; From ac5a9a0c79c7fa33da15208e5d076271b2e433cf Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 5 Aug 2016 13:33:35 +0200 Subject: [PATCH 175/213] Refactoring --- .../C1Console/Forms/Flows/FormFlowRenderingService.cs | 7 ++----- .../C1Console/Forms/Flows/IFormFlowRenderingService.cs | 8 ++++++-- .../FormFlowRendering/FormFlowUiDefinitionRenderer.cs | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Composite/C1Console/Forms/Flows/FormFlowRenderingService.cs b/Composite/C1Console/Forms/Flows/FormFlowRenderingService.cs index 96cb3ee1a0..39b4c8e814 100644 --- a/Composite/C1Console/Forms/Flows/FormFlowRenderingService.cs +++ b/Composite/C1Console/Forms/Flows/FormFlowRenderingService.cs @@ -20,7 +20,7 @@ public void RerenderView() public bool HasFieldMessages { - get { return (_bindingPathedMessages != null && _bindingPathedMessages.Count > 0); } + get { return _bindingPathedMessages != null && _bindingPathedMessages.Count > 0; } } @@ -38,10 +38,7 @@ public void ShowFieldMessages(Dictionary bindingPathedMessages) } - internal Dictionary BindingPathedMessages - { - get { return _bindingPathedMessages; } - } + public Dictionary BindingPathedMessages => _bindingPathedMessages; public void ShowFieldMessage(string fieldBindingPath, string message) diff --git a/Composite/C1Console/Forms/Flows/IFormFlowRenderingService.cs b/Composite/C1Console/Forms/Flows/IFormFlowRenderingService.cs index c87edaad15..e237ea8004 100644 --- a/Composite/C1Console/Forms/Flows/IFormFlowRenderingService.cs +++ b/Composite/C1Console/Forms/Flows/IFormFlowRenderingService.cs @@ -1,8 +1,9 @@ -using Composite.C1Console.Actions; +using System.Collections.Generic; +using Composite.C1Console.Actions; namespace Composite.C1Console.Forms.Flows { - /// + /// /// /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] @@ -22,5 +23,8 @@ public interface IFormFlowRenderingService : IFlowControllerService /// void SetSaveStatus(bool succeeded); + + /// + Dictionary BindingPathedMessages { get; } } } diff --git a/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs b/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs index a9aef096f2..4af3c8d67d 100644 --- a/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs +++ b/Composite/Core/WebClient/FlowMediators/FormFlowRendering/FormFlowUiDefinitionRenderer.cs @@ -155,7 +155,7 @@ private static void BaseEventHandler(string consoleId, return; } - var formFlowService = (FormFlowRenderingService) formServicesContainer.GetService(); + var formFlowService = formServicesContainer.GetService(); bool replacePageOutput = (formServicesContainer.GetService().NewPageOutput != null); bool rerenderView = formFlowService.RerenderViewRequested; From 7ef674c919ac3be30da5203b45dcd530cc4e44d9 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 8 Aug 2016 12:00:50 +0200 Subject: [PATCH 176/213] Bulk publishing - showing "Sent to Draft" status correctly --- Website/Composite/services/Tree/TreeServices.asmx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Website/Composite/services/Tree/TreeServices.asmx b/Website/Composite/services/Tree/TreeServices.asmx index cf6bbc18d6..5c8edeb583 100644 --- a/Website/Composite/services/Tree/TreeServices.asmx +++ b/Website/Composite/services/Tree/TreeServices.asmx @@ -3,7 +3,6 @@ using System; using System.Linq; using System.Collections.Generic; -using System.Globalization; using System.Text.RegularExpressions; using System.Web.Services; using System.Web.Services.Protocols; @@ -15,7 +14,6 @@ using Composite.C1Console.Security; using Composite.C1Console.Users; using Composite.Core; using Composite.Core.IO; -using Composite.Core.ResourceSystem; using Composite.Core.Routing; using Composite.Core.Xml; using Composite.Core.Types; @@ -33,6 +31,8 @@ using Composite.Core.Extensions; using Composite.Plugins.Elements.ElementProviders.MediaFileProviderElementProvider; using Composite.Plugins.Elements.ElementProviders.AllFunctionsElementProvider; +using Texts = Composite.Core.ResourceSystem.LocalizationFiles.Composite_Management; + namespace Composite.Services { [WebService(Namespace = "http://www.composite.net/ns/management")] @@ -91,9 +91,10 @@ namespace Composite.Services var publicationStates = new Dictionary { - {GenericPublishProcessController.Draft, StringResourceSystemFacade.GetString("Composite.Management", "PublishingStatus.draft")}, - {GenericPublishProcessController.AwaitingApproval, StringResourceSystemFacade.GetString("Composite.Management", "PublishingStatus.awaitingApproval")}, - {GenericPublishProcessController.AwaitingPublication, StringResourceSystemFacade.GetString("Composite.Management", "PublishingStatus.awaitingPublication")} + {GenericPublishProcessController.Draft, Texts.PublishingStatus_draft}, + {GenericPublishProcessController.AwaitingApproval, Texts.PublishingStatus_awaitingApproval}, + {GenericPublishProcessController.AwaitingPublication, Texts.PublishingStatus_awaitingPublication}, + {GenericPublishProcessController.SentToDraft, Texts.PublishingStatus_sentToDraft} }; List> actionRequiredPages = From 80c0fa0df85d678ddf1c6be3301eda07a7dfd32f Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Mon, 8 Aug 2016 15:29:29 +0200 Subject: [PATCH 177/213] moving default version name to the core --- .../Core/ResourceSystem/LocalizationFiles.cs | 284 +++++++++--------- Composite/Data/Types/IVersionedDataHelper.cs | 8 +- .../Composite.Management.en-us.xml | 2 + 3 files changed, 151 insertions(+), 143 deletions(-) diff --git a/Composite/Core/ResourceSystem/LocalizationFiles.cs b/Composite/Core/ResourceSystem/LocalizationFiles.cs index a88572b1e6..8a650b7b9a 100644 --- a/Composite/Core/ResourceSystem/LocalizationFiles.cs +++ b/Composite/Core/ResourceSystem/LocalizationFiles.cs @@ -338,15 +338,15 @@ public static class Composite_C1Console_Users { public static string ChangeOwnCultureWorkflow_ElementActionLabel=>T("ChangeOwnCultureWorkflow.ElementActionLabel"); ///"Set the C1 Console language and formatting of numbers, times and dates" public static string ChangeOwnCultureWorkflow_ElementActionToolTip=>T("ChangeOwnCultureWorkflow.ElementActionToolTip"); -///"Regional Settings and Language" +///"Profile Settings" public static string ChangeOwnCultureWorkflow_Dialog_Label=>T("ChangeOwnCultureWorkflow.Dialog.Label"); ///"Display Preferences" public static string ChangeOwnCultureWorkflow_Dialog_CultureSelector_Label=>T("ChangeOwnCultureWorkflow.Dialog.CultureSelector.Label"); -///"Select from the list of options to update how time, date, and number formats are displayed within the console." +///"Display for time, date, and number formats within the console" public static string ChangeOwnCultureWorkflow_Dialog_CultureSelector_Help=>T("ChangeOwnCultureWorkflow.Dialog.CultureSelector.Help"); ///"Console Language Preferences" public static string ChangeOwnCultureWorkflow_Dialog_C1ConsoleLanguageSelector_Label=>T("ChangeOwnCultureWorkflow.Dialog.C1ConsoleLanguageSelector.Label"); -///"Select the language to be used for labels, help texts, dialogs etc. The available options here are limited to languages installed. You may install more languages via the Package system, see Composite.Localization." +///"Language displayed within your console for labels, help texts, dialogs, etc. The available options are limited to language packages installed. See Composite.Localization packages for more language options." public static string ChangeOwnCultureWorkflow_Dialog_C1ConsoleLanguageSelector_Help=>T("ChangeOwnCultureWorkflow.Dialog.C1ConsoleLanguageSelector.Help"); ///"Change application language" public static string ChangeOwnCultureWorkflow_Dialog_Confirm_Title=>T("ChangeOwnCultureWorkflow.Dialog.Confirm.Title"); @@ -1181,13 +1181,13 @@ public static class Composite_Management { public static string Website_Forms_Administrative_EditUserStep1_GroupHelp=>T("Website.Forms.Administrative.EditUserStep1.GroupHelp"); ///"C1 Console Localization" public static string Website_Forms_Administrative_EditUserStep1_LabelLocalizationFieldGroup=>T("Website.Forms.Administrative.EditUserStep1.LabelLocalizationFieldGroup"); -///"Regional settings" +///"Display Preferences" public static string Website_Forms_Administrative_EditUserStep1_CultureLabel=>T("Website.Forms.Administrative.EditUserStep1.CultureLabel"); -///"To change the way numbers, dates, and hours are displayed, select an entry from the list." +///"Display for time, date, and number formats within the console" public static string Website_Forms_Administrative_EditUserStep1_CultureHelp=>T("Website.Forms.Administrative.EditUserStep1.CultureHelp"); -///"C1 Console Language" +///"Console Language Preferences" public static string Website_Forms_Administrative_EditUserStep1_C1ConsoleLanguageLabel=>T("Website.Forms.Administrative.EditUserStep1.C1ConsoleLanguageLabel"); -///"Select the language to be used for labels, help texts, dialogs etc. The available options here are limited to languages installed. You may install more languages via the Package system, see Composite.Localization." +///"Language displayed within your console for labels, help texts, dialogs, etc. The available options are limited to language packages installed. See Composite.Localization packages for more language options." public static string Website_Forms_Administrative_EditUserStep1_C1ConsoleLanguageHelp=>T("Website.Forms.Administrative.EditUserStep1.C1ConsoleLanguageHelp"); ///"Perspectives" public static string Website_Forms_Administrative_EditUserStep1_ActivePerspectiveFieldLabel=>T("Website.Forms.Administrative.EditUserStep1.ActivePerspectiveFieldLabel"); @@ -1287,13 +1287,13 @@ public static class Composite_Management { public static string Website_Forms_Administrative_AddNewUserStep1_GroupLabel=>T("Website.Forms.Administrative.AddNewUserStep1.GroupLabel"); ///"If you enter a folder name that does not already exist a new folder will be created." public static string Website_Forms_Administrative_AddNewUserStep1_GroupHelp=>T("Website.Forms.Administrative.AddNewUserStep1.GroupHelp"); -///"Regional settings" +///"Display Preferences" public static string Website_Forms_Administrative_AddNewUserStep1_CultureLabel=>T("Website.Forms.Administrative.AddNewUserStep1.CultureLabel"); -///"To change the way numbers, dates, and hours are displayed, select an entry from the list." +///"Display for time, date, and number formats within the console" public static string Website_Forms_Administrative_AddNewUserStep1_CultureHelp=>T("Website.Forms.Administrative.AddNewUserStep1.CultureHelp"); -///"C1 Console Language" +///"Console Language Preferences" public static string Website_Forms_Administrative_AddNewUserStep1_C1ConsoleLanguageLabel=>T("Website.Forms.Administrative.AddNewUserStep1.C1ConsoleLanguageLabel"); -///"Select the language to be used for labels, help texts, dialogs etc. The available options here are limited to languages installed. You may install more languages via the Package system, see Composite.Localization." +///"Language displayed within your console for labels, help texts, dialogs, etc. The available options are limited to language packages installed. See Composite.Localization packages for more language options." public static string Website_Forms_Administrative_AddNewUserStep1_C1ConsoleLanguageHelp=>T("Website.Forms.Administrative.AddNewUserStep1.C1ConsoleLanguageHelp"); ///"A language is required" public static string UserElementProvider_MissingActiveLanguageTitle=>T("UserElementProvider.MissingActiveLanguageTitle"); @@ -2455,6 +2455,8 @@ public static class Composite_Management { public static string Browser_Label=>T("Browser.Label"); ///"Browse unpublished pages" public static string Browser_ToolTip=>T("Browser.ToolTip"); +///"Original" +public static string DefaultVersionName=>T("DefaultVersionName"); private static string T(string key) { return StringResourceSystemFacade.GetString("Composite.Management", key); @@ -5214,266 +5216,266 @@ private static string T(string key) /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public static class Composite_Plugins_TimezoneAbbriviations { + public static class Composite_Plugins_TimezoneAbbreviations { ///"Etc/GMT+12" -public static string TimezoneAbbriviations_Dateline_Standard_Time=>T("TimezoneAbbriviations.Dateline Standard Time"); +public static string TimezoneAbbreviations_Dateline_Standard_Time=>T("TimezoneAbbreviations.Dateline Standard Time"); ///"Etc/GMT+11" -public static string TimezoneAbbriviations_UTC_11=>T("TimezoneAbbriviations.UTC-11"); +public static string TimezoneAbbreviations_UTC_11=>T("TimezoneAbbreviations.UTC-11"); ///"UTC-10" -public static string TimezoneAbbriviations_Aleutian_Standard_Time=>T("TimezoneAbbriviations.Aleutian Standard Time"); +public static string TimezoneAbbreviations_Aleutian_Standard_Time=>T("TimezoneAbbreviations.Aleutian Standard Time"); ///"HST" -public static string TimezoneAbbriviations_Hawaiian_Standard_Time=>T("TimezoneAbbriviations.Hawaiian Standard Time"); +public static string TimezoneAbbreviations_Hawaiian_Standard_Time=>T("TimezoneAbbreviations.Hawaiian Standard Time"); ///"MART" -public static string TimezoneAbbriviations_Marquesas_Standard_Time=>T("TimezoneAbbriviations.Marquesas Standard Time"); +public static string TimezoneAbbreviations_Marquesas_Standard_Time=>T("TimezoneAbbreviations.Marquesas Standard Time"); ///"AKST" -public static string TimezoneAbbriviations_Alaskan_Standard_Time=>T("TimezoneAbbriviations.Alaskan Standard Time"); +public static string TimezoneAbbreviations_Alaskan_Standard_Time=>T("TimezoneAbbreviations.Alaskan Standard Time"); ///"UTC-09" -public static string TimezoneAbbriviations_UTC_09=>T("TimezoneAbbriviations.UTC-09"); +public static string TimezoneAbbreviations_UTC_09=>T("TimezoneAbbreviations.UTC-09"); ///"PST" -public static string TimezoneAbbriviations_Pacific_Standard_Time_Mexico=>T("TimezoneAbbriviations.Pacific Standard Time (Mexico)"); +public static string TimezoneAbbreviations_Pacific_Standard_Time_Mexico=>T("TimezoneAbbreviations.Pacific Standard Time (Mexico)"); ///"UTC-08" -public static string TimezoneAbbriviations_UTC_08=>T("TimezoneAbbriviations.UTC-08"); +public static string TimezoneAbbreviations_UTC_08=>T("TimezoneAbbreviations.UTC-08"); ///"PST" -public static string TimezoneAbbriviations_Pacific_Standard_Time=>T("TimezoneAbbriviations.Pacific Standard Time"); +public static string TimezoneAbbreviations_Pacific_Standard_Time=>T("TimezoneAbbreviations.Pacific Standard Time"); ///"MST" -public static string TimezoneAbbriviations_US_Mountain_Standard_Time=>T("TimezoneAbbriviations.US Mountain Standard Time"); +public static string TimezoneAbbreviations_US_Mountain_Standard_Time=>T("TimezoneAbbreviations.US Mountain Standard Time"); ///"MST" -public static string TimezoneAbbriviations_Mountain_Standard_Time_Mexico=>T("TimezoneAbbriviations.Mountain Standard Time (Mexico)"); +public static string TimezoneAbbreviations_Mountain_Standard_Time_Mexico=>T("TimezoneAbbreviations.Mountain Standard Time (Mexico)"); ///"MST" -public static string TimezoneAbbriviations_Mountain_Standard_Time=>T("TimezoneAbbriviations.Mountain Standard Time"); +public static string TimezoneAbbreviations_Mountain_Standard_Time=>T("TimezoneAbbreviations.Mountain Standard Time"); ///"CST" -public static string TimezoneAbbriviations_Central_America_Standard_Time=>T("TimezoneAbbriviations.Central America Standard Time"); +public static string TimezoneAbbreviations_Central_America_Standard_Time=>T("TimezoneAbbreviations.Central America Standard Time"); ///"CST" -public static string TimezoneAbbriviations_Central_Standard_Time=>T("TimezoneAbbriviations.Central Standard Time"); +public static string TimezoneAbbreviations_Central_Standard_Time=>T("TimezoneAbbreviations.Central Standard Time"); ///"EASST" -public static string TimezoneAbbriviations_Easter_Island_Standard_Time=>T("TimezoneAbbriviations.Easter Island Standard Time"); +public static string TimezoneAbbreviations_Easter_Island_Standard_Time=>T("TimezoneAbbreviations.Easter Island Standard Time"); ///"CST" -public static string TimezoneAbbriviations_Central_Standard_Time_Mexico=>T("TimezoneAbbriviations.Central Standard Time (Mexico)"); +public static string TimezoneAbbreviations_Central_Standard_Time_Mexico=>T("TimezoneAbbreviations.Central Standard Time (Mexico)"); ///"CST" -public static string TimezoneAbbriviations_Canada_Central_Standard_Time=>T("TimezoneAbbriviations.Canada Central Standard Time"); +public static string TimezoneAbbreviations_Canada_Central_Standard_Time=>T("TimezoneAbbreviations.Canada Central Standard Time"); ///"SAPST" -public static string TimezoneAbbriviations_SA_Pacific_Standard_Time=>T("TimezoneAbbriviations.SA Pacific Standard Time"); +public static string TimezoneAbbreviations_SA_Pacific_Standard_Time=>T("TimezoneAbbreviations.SA Pacific Standard Time"); ///"EST" -public static string TimezoneAbbriviations_Eastern_Standard_Time_Mexico=>T("TimezoneAbbriviations.Eastern Standard Time (Mexico)"); +public static string TimezoneAbbreviations_Eastern_Standard_Time_Mexico=>T("TimezoneAbbreviations.Eastern Standard Time (Mexico)"); ///"EST" -public static string TimezoneAbbriviations_Eastern_Standard_Time=>T("TimezoneAbbriviations.Eastern Standard Time"); +public static string TimezoneAbbreviations_Eastern_Standard_Time=>T("TimezoneAbbreviations.Eastern Standard Time"); ///"EST" -public static string TimezoneAbbriviations_Haiti_Standard_Time=>T("TimezoneAbbriviations.Haiti Standard Time"); +public static string TimezoneAbbreviations_Haiti_Standard_Time=>T("TimezoneAbbreviations.Haiti Standard Time"); ///"UTC-05" -public static string TimezoneAbbriviations_Cuba_Standard_Time=>T("TimezoneAbbriviations.Cuba Standard Time"); +public static string TimezoneAbbreviations_Cuba_Standard_Time=>T("TimezoneAbbreviations.Cuba Standard Time"); ///"EST" -public static string TimezoneAbbriviations_US_Eastern_Standard_Time=>T("TimezoneAbbriviations.US Eastern Standard Time"); +public static string TimezoneAbbreviations_US_Eastern_Standard_Time=>T("TimezoneAbbreviations.US Eastern Standard Time"); ///"VET" -public static string TimezoneAbbriviations_Venezuela_Standard_Time=>T("TimezoneAbbriviations.Venezuela Standard Time"); +public static string TimezoneAbbreviations_Venezuela_Standard_Time=>T("TimezoneAbbreviations.Venezuela Standard Time"); ///"PYST" -public static string TimezoneAbbriviations_Paraguay_Standard_Time=>T("TimezoneAbbriviations.Paraguay Standard Time"); +public static string TimezoneAbbreviations_Paraguay_Standard_Time=>T("TimezoneAbbreviations.Paraguay Standard Time"); ///"AST" -public static string TimezoneAbbriviations_Atlantic_Standard_Time=>T("TimezoneAbbriviations.Atlantic Standard Time"); +public static string TimezoneAbbreviations_Atlantic_Standard_Time=>T("TimezoneAbbreviations.Atlantic Standard Time"); ///"AMST" -public static string TimezoneAbbriviations_Central_Brazilian_Standard_Time=>T("TimezoneAbbriviations.Central Brazilian Standard Time"); +public static string TimezoneAbbreviations_Central_Brazilian_Standard_Time=>T("TimezoneAbbreviations.Central Brazilian Standard Time"); ///"SAWST" -public static string TimezoneAbbriviations_SA_Western_Standard_Time=>T("TimezoneAbbriviations.SA Western Standard Time"); +public static string TimezoneAbbreviations_SA_Western_Standard_Time=>T("TimezoneAbbreviations.SA Western Standard Time"); ///"CLST" -public static string TimezoneAbbriviations_Pacific_SA_Standard_Time=>T("TimezoneAbbriviations.Pacific SA Standard Time"); +public static string TimezoneAbbreviations_Pacific_SA_Standard_Time=>T("TimezoneAbbreviations.Pacific SA Standard Time"); ///"UTC-04" -public static string TimezoneAbbriviations_Turks_And_Caicos_Standard_Time=>T("TimezoneAbbriviations.Turks And Caicos Standard Time"); +public static string TimezoneAbbreviations_Turks_And_Caicos_Standard_Time=>T("TimezoneAbbreviations.Turks And Caicos Standard Time"); ///"NST" -public static string TimezoneAbbriviations_Newfoundland_Standard_Time=>T("TimezoneAbbriviations.Newfoundland Standard Time"); +public static string TimezoneAbbreviations_Newfoundland_Standard_Time=>T("TimezoneAbbreviations.Newfoundland Standard Time"); ///"UTC-03" -public static string TimezoneAbbriviations_Tocantins_Standard_Time=>T("TimezoneAbbriviations.Tocantins Standard Time"); +public static string TimezoneAbbreviations_Tocantins_Standard_Time=>T("TimezoneAbbreviations.Tocantins Standard Time"); ///"BRST" -public static string TimezoneAbbriviations_E__South_America_Standard_Time=>T("TimezoneAbbriviations.E. South America Standard Time"); +public static string TimezoneAbbreviations_E__South_America_Standard_Time=>T("TimezoneAbbreviations.E. South America Standard Time"); ///"GFT" -public static string TimezoneAbbriviations_SA_Eastern_Standard_Time=>T("TimezoneAbbriviations.SA Eastern Standard Time"); +public static string TimezoneAbbreviations_SA_Eastern_Standard_Time=>T("TimezoneAbbreviations.SA Eastern Standard Time"); ///"ART" -public static string TimezoneAbbriviations_Argentina_Standard_Time=>T("TimezoneAbbriviations.Argentina Standard Time"); +public static string TimezoneAbbreviations_Argentina_Standard_Time=>T("TimezoneAbbreviations.Argentina Standard Time"); ///"WGT" -public static string TimezoneAbbriviations_Greenland_Standard_Time=>T("TimezoneAbbriviations.Greenland Standard Time"); +public static string TimezoneAbbreviations_Greenland_Standard_Time=>T("TimezoneAbbreviations.Greenland Standard Time"); ///"UYT" -public static string TimezoneAbbriviations_Montevideo_Standard_Time=>T("TimezoneAbbriviations.Montevideo Standard Time"); +public static string TimezoneAbbreviations_Montevideo_Standard_Time=>T("TimezoneAbbreviations.Montevideo Standard Time"); ///"UTC-03" -public static string TimezoneAbbriviations_Saint_Pierre_Standard_Time=>T("TimezoneAbbriviations.Saint Pierre Standard Time"); +public static string TimezoneAbbreviations_Saint_Pierre_Standard_Time=>T("TimezoneAbbreviations.Saint Pierre Standard Time"); ///"BRT" -public static string TimezoneAbbriviations_Bahia_Standard_Time=>T("TimezoneAbbriviations.Bahia Standard Time"); +public static string TimezoneAbbreviations_Bahia_Standard_Time=>T("TimezoneAbbreviations.Bahia Standard Time"); ///"Etc/GMT+2" -public static string TimezoneAbbriviations_UTC_02=>T("TimezoneAbbriviations.UTC-02"); +public static string TimezoneAbbreviations_UTC_02=>T("TimezoneAbbreviations.UTC-02"); ///"AST" -public static string TimezoneAbbriviations_Mid_Atlantic_Standard_Time=>T("TimezoneAbbriviations.Mid-Atlantic Standard Time"); +public static string TimezoneAbbreviations_Mid_Atlantic_Standard_Time=>T("TimezoneAbbreviations.Mid-Atlantic Standard Time"); ///"AZOT" -public static string TimezoneAbbriviations_Azores_Standard_Time=>T("TimezoneAbbriviations.Azores Standard Time"); +public static string TimezoneAbbreviations_Azores_Standard_Time=>T("TimezoneAbbreviations.Azores Standard Time"); ///"CVT" -public static string TimezoneAbbriviations_Cape_Verde_Standard_Time=>T("TimezoneAbbriviations.Cape Verde Standard Time"); +public static string TimezoneAbbreviations_Cape_Verde_Standard_Time=>T("TimezoneAbbreviations.Cape Verde Standard Time"); ///"WET" -public static string TimezoneAbbriviations_Morocco_Standard_Time=>T("TimezoneAbbriviations.Morocco Standard Time"); +public static string TimezoneAbbreviations_Morocco_Standard_Time=>T("TimezoneAbbreviations.Morocco Standard Time"); ///"Etc/GMT" -public static string TimezoneAbbriviations_UTC=>T("TimezoneAbbriviations.UTC"); +public static string TimezoneAbbreviations_UTC=>T("TimezoneAbbreviations.UTC"); ///"GMT" -public static string TimezoneAbbriviations_GMT_Standard_Time=>T("TimezoneAbbriviations.GMT Standard Time"); +public static string TimezoneAbbreviations_GMT_Standard_Time=>T("TimezoneAbbreviations.GMT Standard Time"); ///"GMT" -public static string TimezoneAbbriviations_Greenwich_Standard_Time=>T("TimezoneAbbriviations.Greenwich Standard Time"); +public static string TimezoneAbbreviations_Greenwich_Standard_Time=>T("TimezoneAbbreviations.Greenwich Standard Time"); ///"CET" -public static string TimezoneAbbriviations_W__Europe_Standard_Time=>T("TimezoneAbbriviations.W. Europe Standard Time"); +public static string TimezoneAbbreviations_W__Europe_Standard_Time=>T("TimezoneAbbreviations.W. Europe Standard Time"); ///"CET" -public static string TimezoneAbbriviations_Central_Europe_Standard_Time=>T("TimezoneAbbriviations.Central Europe Standard Time"); +public static string TimezoneAbbreviations_Central_Europe_Standard_Time=>T("TimezoneAbbreviations.Central Europe Standard Time"); ///"CET" -public static string TimezoneAbbriviations_Romance_Standard_Time=>T("TimezoneAbbriviations.Romance Standard Time"); +public static string TimezoneAbbreviations_Romance_Standard_Time=>T("TimezoneAbbreviations.Romance Standard Time"); ///"CET" -public static string TimezoneAbbriviations_Central_European_Standard_Time=>T("TimezoneAbbriviations.Central European Standard Time"); +public static string TimezoneAbbreviations_Central_European_Standard_Time=>T("TimezoneAbbreviations.Central European Standard Time"); ///"WAT" -public static string TimezoneAbbriviations_W__Central_Africa_Standard_Time=>T("TimezoneAbbriviations.W. Central Africa Standard Time"); +public static string TimezoneAbbreviations_W__Central_Africa_Standard_Time=>T("TimezoneAbbreviations.W. Central Africa Standard Time"); ///"WAST" -public static string TimezoneAbbriviations_Namibia_Standard_Time=>T("TimezoneAbbriviations.Namibia Standard Time"); +public static string TimezoneAbbreviations_Namibia_Standard_Time=>T("TimezoneAbbreviations.Namibia Standard Time"); ///"EET" -public static string TimezoneAbbriviations_Jordan_Standard_Time=>T("TimezoneAbbriviations.Jordan Standard Time"); +public static string TimezoneAbbreviations_Jordan_Standard_Time=>T("TimezoneAbbreviations.Jordan Standard Time"); ///"EET" -public static string TimezoneAbbriviations_GTB_Standard_Time=>T("TimezoneAbbriviations.GTB Standard Time"); +public static string TimezoneAbbreviations_GTB_Standard_Time=>T("TimezoneAbbreviations.GTB Standard Time"); ///"EET" -public static string TimezoneAbbriviations_Middle_East_Standard_Time=>T("TimezoneAbbriviations.Middle East Standard Time"); +public static string TimezoneAbbreviations_Middle_East_Standard_Time=>T("TimezoneAbbreviations.Middle East Standard Time"); ///"EET" -public static string TimezoneAbbriviations_Egypt_Standard_Time=>T("TimezoneAbbriviations.Egypt Standard Time"); +public static string TimezoneAbbreviations_Egypt_Standard_Time=>T("TimezoneAbbreviations.Egypt Standard Time"); ///"EET" -public static string TimezoneAbbriviations_Syria_Standard_Time=>T("TimezoneAbbriviations.Syria Standard Time"); +public static string TimezoneAbbreviations_Syria_Standard_Time=>T("TimezoneAbbreviations.Syria Standard Time"); ///"EET" -public static string TimezoneAbbriviations_E__Europe_Standard_Time=>T("TimezoneAbbriviations.E. Europe Standard Time"); +public static string TimezoneAbbreviations_E__Europe_Standard_Time=>T("TimezoneAbbreviations.E. Europe Standard Time"); ///"UTC+02" -public static string TimezoneAbbriviations_West_Bank_Standard_Time=>T("TimezoneAbbriviations.West Bank Standard Time"); +public static string TimezoneAbbreviations_West_Bank_Standard_Time=>T("TimezoneAbbreviations.West Bank Standard Time"); ///"SAST" -public static string TimezoneAbbriviations_South_Africa_Standard_Time=>T("TimezoneAbbriviations.South Africa Standard Time"); +public static string TimezoneAbbreviations_South_Africa_Standard_Time=>T("TimezoneAbbreviations.South Africa Standard Time"); ///"EET" -public static string TimezoneAbbriviations_FLE_Standard_Time=>T("TimezoneAbbriviations.FLE Standard Time"); +public static string TimezoneAbbreviations_FLE_Standard_Time=>T("TimezoneAbbreviations.FLE Standard Time"); ///"EET" -public static string TimezoneAbbriviations_Turkey_Standard_Time=>T("TimezoneAbbriviations.Turkey Standard Time"); +public static string TimezoneAbbreviations_Turkey_Standard_Time=>T("TimezoneAbbreviations.Turkey Standard Time"); ///"IST" -public static string TimezoneAbbriviations_Israel_Standard_Time=>T("TimezoneAbbriviations.Israel Standard Time"); +public static string TimezoneAbbreviations_Israel_Standard_Time=>T("TimezoneAbbreviations.Israel Standard Time"); ///"EET" -public static string TimezoneAbbriviations_Kaliningrad_Standard_Time=>T("TimezoneAbbriviations.Kaliningrad Standard Time"); +public static string TimezoneAbbreviations_Kaliningrad_Standard_Time=>T("TimezoneAbbreviations.Kaliningrad Standard Time"); ///"EET" -public static string TimezoneAbbriviations_Libya_Standard_Time=>T("TimezoneAbbriviations.Libya Standard Time"); +public static string TimezoneAbbreviations_Libya_Standard_Time=>T("TimezoneAbbreviations.Libya Standard Time"); ///"AST" -public static string TimezoneAbbriviations_Arabic_Standard_Time=>T("TimezoneAbbriviations.Arabic Standard Time"); +public static string TimezoneAbbreviations_Arabic_Standard_Time=>T("TimezoneAbbreviations.Arabic Standard Time"); ///"AST" -public static string TimezoneAbbriviations_Arab_Standard_Time=>T("TimezoneAbbriviations.Arab Standard Time"); +public static string TimezoneAbbreviations_Arab_Standard_Time=>T("TimezoneAbbreviations.Arab Standard Time"); ///"MSK" -public static string TimezoneAbbriviations_Belarus_Standard_Time=>T("TimezoneAbbriviations.Belarus Standard Time"); +public static string TimezoneAbbreviations_Belarus_Standard_Time=>T("TimezoneAbbreviations.Belarus Standard Time"); ///"MSK" -public static string TimezoneAbbriviations_Russian_Standard_Time=>T("TimezoneAbbriviations.Russian Standard Time"); +public static string TimezoneAbbreviations_Russian_Standard_Time=>T("TimezoneAbbreviations.Russian Standard Time"); ///"EAT" -public static string TimezoneAbbriviations_E__Africa_Standard_Time=>T("TimezoneAbbriviations.E. Africa Standard Time"); +public static string TimezoneAbbreviations_E__Africa_Standard_Time=>T("TimezoneAbbreviations.E. Africa Standard Time"); ///"MSK" -public static string TimezoneAbbriviations_Astrakhan_Standard_Time=>T("TimezoneAbbriviations.Astrakhan Standard Time"); +public static string TimezoneAbbreviations_Astrakhan_Standard_Time=>T("TimezoneAbbreviations.Astrakhan Standard Time"); ///"IRST" -public static string TimezoneAbbriviations_Iran_Standard_Time=>T("TimezoneAbbriviations.Iran Standard Time"); +public static string TimezoneAbbreviations_Iran_Standard_Time=>T("TimezoneAbbreviations.Iran Standard Time"); ///"GST" -public static string TimezoneAbbriviations_Arabian_Standard_Time=>T("TimezoneAbbriviations.Arabian Standard Time"); +public static string TimezoneAbbreviations_Arabian_Standard_Time=>T("TimezoneAbbreviations.Arabian Standard Time"); ///"AZT" -public static string TimezoneAbbriviations_Azerbaijan_Standard_Time=>T("TimezoneAbbriviations.Azerbaijan Standard Time"); +public static string TimezoneAbbreviations_Azerbaijan_Standard_Time=>T("TimezoneAbbreviations.Azerbaijan Standard Time"); ///"SAMT" -public static string TimezoneAbbriviations_Russia_Time_Zone_3=>T("TimezoneAbbriviations.Russia Time Zone 3"); +public static string TimezoneAbbreviations_Russia_Time_Zone_3=>T("TimezoneAbbreviations.Russia Time Zone 3"); ///"MUT" -public static string TimezoneAbbriviations_Mauritius_Standard_Time=>T("TimezoneAbbriviations.Mauritius Standard Time"); +public static string TimezoneAbbreviations_Mauritius_Standard_Time=>T("TimezoneAbbreviations.Mauritius Standard Time"); ///"GET" -public static string TimezoneAbbriviations_Georgian_Standard_Time=>T("TimezoneAbbriviations.Georgian Standard Time"); +public static string TimezoneAbbreviations_Georgian_Standard_Time=>T("TimezoneAbbreviations.Georgian Standard Time"); ///"AMT" -public static string TimezoneAbbriviations_Caucasus_Standard_Time=>T("TimezoneAbbriviations.Caucasus Standard Time"); +public static string TimezoneAbbreviations_Caucasus_Standard_Time=>T("TimezoneAbbreviations.Caucasus Standard Time"); ///"AFT" -public static string TimezoneAbbriviations_Afghanistan_Standard_Time=>T("TimezoneAbbriviations.Afghanistan Standard Time"); +public static string TimezoneAbbreviations_Afghanistan_Standard_Time=>T("TimezoneAbbreviations.Afghanistan Standard Time"); ///"UZT" -public static string TimezoneAbbriviations_West_Asia_Standard_Time=>T("TimezoneAbbriviations.West Asia Standard Time"); +public static string TimezoneAbbreviations_West_Asia_Standard_Time=>T("TimezoneAbbreviations.West Asia Standard Time"); ///"YEKT" -public static string TimezoneAbbriviations_Ekaterinburg_Standard_Time=>T("TimezoneAbbriviations.Ekaterinburg Standard Time"); +public static string TimezoneAbbreviations_Ekaterinburg_Standard_Time=>T("TimezoneAbbreviations.Ekaterinburg Standard Time"); ///"PKT" -public static string TimezoneAbbriviations_Pakistan_Standard_Time=>T("TimezoneAbbriviations.Pakistan Standard Time"); +public static string TimezoneAbbreviations_Pakistan_Standard_Time=>T("TimezoneAbbreviations.Pakistan Standard Time"); ///"IST" -public static string TimezoneAbbriviations_India_Standard_Time=>T("TimezoneAbbriviations.India Standard Time"); +public static string TimezoneAbbreviations_India_Standard_Time=>T("TimezoneAbbreviations.India Standard Time"); ///"IST" -public static string TimezoneAbbriviations_Sri_Lanka_Standard_Time=>T("TimezoneAbbriviations.Sri Lanka Standard Time"); +public static string TimezoneAbbreviations_Sri_Lanka_Standard_Time=>T("TimezoneAbbreviations.Sri Lanka Standard Time"); ///"NPT" -public static string TimezoneAbbriviations_Nepal_Standard_Time=>T("TimezoneAbbriviations.Nepal Standard Time"); +public static string TimezoneAbbreviations_Nepal_Standard_Time=>T("TimezoneAbbreviations.Nepal Standard Time"); ///"ALMT" -public static string TimezoneAbbriviations_Central_Asia_Standard_Time=>T("TimezoneAbbriviations.Central Asia Standard Time"); +public static string TimezoneAbbreviations_Central_Asia_Standard_Time=>T("TimezoneAbbreviations.Central Asia Standard Time"); ///"BDT" -public static string TimezoneAbbriviations_Bangladesh_Standard_Time=>T("TimezoneAbbriviations.Bangladesh Standard Time"); +public static string TimezoneAbbreviations_Bangladesh_Standard_Time=>T("TimezoneAbbreviations.Bangladesh Standard Time"); ///"NOVT" -public static string TimezoneAbbriviations_N__Central_Asia_Standard_Time=>T("TimezoneAbbriviations.N. Central Asia Standard Time"); +public static string TimezoneAbbreviations_N__Central_Asia_Standard_Time=>T("TimezoneAbbreviations.N. Central Asia Standard Time"); ///"MSK+3" -public static string TimezoneAbbriviations_Altai_Standard_Time=>T("TimezoneAbbriviations.Altai Standard Time"); +public static string TimezoneAbbreviations_Altai_Standard_Time=>T("TimezoneAbbreviations.Altai Standard Time"); ///"MMT" -public static string TimezoneAbbriviations_Myanmar_Standard_Time=>T("TimezoneAbbriviations.Myanmar Standard Time"); +public static string TimezoneAbbreviations_Myanmar_Standard_Time=>T("TimezoneAbbreviations.Myanmar Standard Time"); ///"ICT" -public static string TimezoneAbbriviations_SE_Asia_Standard_Time=>T("TimezoneAbbriviations.SE Asia Standard Time"); +public static string TimezoneAbbreviations_SE_Asia_Standard_Time=>T("TimezoneAbbreviations.SE Asia Standard Time"); ///"UTC+07" -public static string TimezoneAbbriviations_W__Mongolia_Standard_Time=>T("TimezoneAbbriviations.W. Mongolia Standard Time"); +public static string TimezoneAbbreviations_W__Mongolia_Standard_Time=>T("TimezoneAbbreviations.W. Mongolia Standard Time"); ///"KRAT" -public static string TimezoneAbbriviations_North_Asia_Standard_Time=>T("TimezoneAbbriviations.North Asia Standard Time"); +public static string TimezoneAbbreviations_North_Asia_Standard_Time=>T("TimezoneAbbreviations.North Asia Standard Time"); ///"UTC+07" -public static string TimezoneAbbriviations_Tomsk_Standard_Time=>T("TimezoneAbbriviations.Tomsk Standard Time"); +public static string TimezoneAbbreviations_Tomsk_Standard_Time=>T("TimezoneAbbreviations.Tomsk Standard Time"); ///"CST" -public static string TimezoneAbbriviations_China_Standard_Time=>T("TimezoneAbbriviations.China Standard Time"); +public static string TimezoneAbbreviations_China_Standard_Time=>T("TimezoneAbbreviations.China Standard Time"); ///"IRKT" -public static string TimezoneAbbriviations_North_Asia_East_Standard_Time=>T("TimezoneAbbriviations.North Asia East Standard Time"); +public static string TimezoneAbbreviations_North_Asia_East_Standard_Time=>T("TimezoneAbbreviations.North Asia East Standard Time"); ///"SGT" -public static string TimezoneAbbriviations_Singapore_Standard_Time=>T("TimezoneAbbriviations.Singapore Standard Time"); +public static string TimezoneAbbreviations_Singapore_Standard_Time=>T("TimezoneAbbreviations.Singapore Standard Time"); ///"AWST" -public static string TimezoneAbbriviations_W__Australia_Standard_Time=>T("TimezoneAbbriviations.W. Australia Standard Time"); +public static string TimezoneAbbreviations_W__Australia_Standard_Time=>T("TimezoneAbbreviations.W. Australia Standard Time"); ///"CST" -public static string TimezoneAbbriviations_Taipei_Standard_Time=>T("TimezoneAbbriviations.Taipei Standard Time"); +public static string TimezoneAbbreviations_Taipei_Standard_Time=>T("TimezoneAbbreviations.Taipei Standard Time"); ///"ULAT" -public static string TimezoneAbbriviations_Ulaanbaatar_Standard_Time=>T("TimezoneAbbriviations.Ulaanbaatar Standard Time"); +public static string TimezoneAbbreviations_Ulaanbaatar_Standard_Time=>T("TimezoneAbbreviations.Ulaanbaatar Standard Time"); ///"KST" -public static string TimezoneAbbriviations_North_Korea_Standard_Time=>T("TimezoneAbbriviations.North Korea Standard Time"); +public static string TimezoneAbbreviations_North_Korea_Standard_Time=>T("TimezoneAbbreviations.North Korea Standard Time"); ///"UTC+09" -public static string TimezoneAbbriviations_Transbaikal_Standard_Time=>T("TimezoneAbbriviations.Transbaikal Standard Time"); +public static string TimezoneAbbreviations_Transbaikal_Standard_Time=>T("TimezoneAbbreviations.Transbaikal Standard Time"); ///"JST" -public static string TimezoneAbbriviations_Tokyo_Standard_Time=>T("TimezoneAbbriviations.Tokyo Standard Time"); +public static string TimezoneAbbreviations_Tokyo_Standard_Time=>T("TimezoneAbbreviations.Tokyo Standard Time"); ///"KST" -public static string TimezoneAbbriviations_Korea_Standard_Time=>T("TimezoneAbbriviations.Korea Standard Time"); +public static string TimezoneAbbreviations_Korea_Standard_Time=>T("TimezoneAbbreviations.Korea Standard Time"); ///"YAKT" -public static string TimezoneAbbriviations_Yakutsk_Standard_Time=>T("TimezoneAbbriviations.Yakutsk Standard Time"); +public static string TimezoneAbbreviations_Yakutsk_Standard_Time=>T("TimezoneAbbreviations.Yakutsk Standard Time"); ///"ACDT" -public static string TimezoneAbbriviations_Cen__Australia_Standard_Time=>T("TimezoneAbbriviations.Cen. Australia Standard Time"); +public static string TimezoneAbbreviations_Cen__Australia_Standard_Time=>T("TimezoneAbbreviations.Cen. Australia Standard Time"); ///"ACST" -public static string TimezoneAbbriviations_AUS_Central_Standard_Time=>T("TimezoneAbbriviations.AUS Central Standard Time"); +public static string TimezoneAbbreviations_AUS_Central_Standard_Time=>T("TimezoneAbbreviations.AUS Central Standard Time"); ///"AEST" -public static string TimezoneAbbriviations_E__Australia_Standard_Time=>T("TimezoneAbbriviations.E. Australia Standard Time"); +public static string TimezoneAbbreviations_E__Australia_Standard_Time=>T("TimezoneAbbreviations.E. Australia Standard Time"); ///"AEDT" -public static string TimezoneAbbriviations_AUS_Eastern_Standard_Time=>T("TimezoneAbbriviations.AUS Eastern Standard Time"); +public static string TimezoneAbbreviations_AUS_Eastern_Standard_Time=>T("TimezoneAbbreviations.AUS Eastern Standard Time"); ///"PGT" -public static string TimezoneAbbriviations_West_Pacific_Standard_Time=>T("TimezoneAbbriviations.West Pacific Standard Time"); +public static string TimezoneAbbreviations_West_Pacific_Standard_Time=>T("TimezoneAbbreviations.West Pacific Standard Time"); ///"AEDT" -public static string TimezoneAbbriviations_Tasmania_Standard_Time=>T("TimezoneAbbriviations.Tasmania Standard Time"); +public static string TimezoneAbbreviations_Tasmania_Standard_Time=>T("TimezoneAbbreviations.Tasmania Standard Time"); ///"MAGT" -public static string TimezoneAbbriviations_Magadan_Standard_Time=>T("TimezoneAbbriviations.Magadan Standard Time"); +public static string TimezoneAbbreviations_Magadan_Standard_Time=>T("TimezoneAbbreviations.Magadan Standard Time"); ///"VLAT" -public static string TimezoneAbbriviations_Vladivostok_Standard_Time=>T("TimezoneAbbriviations.Vladivostok Standard Time"); +public static string TimezoneAbbreviations_Vladivostok_Standard_Time=>T("TimezoneAbbreviations.Vladivostok Standard Time"); ///"SRET" -public static string TimezoneAbbriviations_Russia_Time_Zone_10=>T("TimezoneAbbriviations.Russia Time Zone 10"); +public static string TimezoneAbbreviations_Russia_Time_Zone_10=>T("TimezoneAbbreviations.Russia Time Zone 10"); ///"UTC+11" -public static string TimezoneAbbriviations_Norfolk_Standard_Time=>T("TimezoneAbbriviations.Norfolk Standard Time"); +public static string TimezoneAbbreviations_Norfolk_Standard_Time=>T("TimezoneAbbreviations.Norfolk Standard Time"); ///"UTC+11" -public static string TimezoneAbbriviations_Sakhalin_Standard_Time=>T("TimezoneAbbriviations.Sakhalin Standard Time"); +public static string TimezoneAbbreviations_Sakhalin_Standard_Time=>T("TimezoneAbbreviations.Sakhalin Standard Time"); ///"SBT" -public static string TimezoneAbbriviations_Central_Pacific_Standard_Time=>T("TimezoneAbbriviations.Central Pacific Standard Time"); +public static string TimezoneAbbreviations_Central_Pacific_Standard_Time=>T("TimezoneAbbreviations.Central Pacific Standard Time"); ///"PETT" -public static string TimezoneAbbriviations_Russia_Time_Zone_11=>T("TimezoneAbbriviations.Russia Time Zone 11"); +public static string TimezoneAbbreviations_Russia_Time_Zone_11=>T("TimezoneAbbreviations.Russia Time Zone 11"); ///"NZDT" -public static string TimezoneAbbriviations_New_Zealand_Standard_Time=>T("TimezoneAbbriviations.New Zealand Standard Time"); +public static string TimezoneAbbreviations_New_Zealand_Standard_Time=>T("TimezoneAbbreviations.New Zealand Standard Time"); ///"Etc/GMT-12" -public static string TimezoneAbbriviations_UTC12=>T("TimezoneAbbriviations.UTC+12"); +public static string TimezoneAbbreviations_UTC12=>T("TimezoneAbbreviations.UTC+12"); ///"FJST" -public static string TimezoneAbbriviations_Fiji_Standard_Time=>T("TimezoneAbbriviations.Fiji Standard Time"); +public static string TimezoneAbbreviations_Fiji_Standard_Time=>T("TimezoneAbbreviations.Fiji Standard Time"); ///"PETT" -public static string TimezoneAbbriviations_Kamchatka_Standard_Time=>T("TimezoneAbbriviations.Kamchatka Standard Time"); +public static string TimezoneAbbreviations_Kamchatka_Standard_Time=>T("TimezoneAbbreviations.Kamchatka Standard Time"); ///"CHAST" -public static string TimezoneAbbriviations_Chatham_Islands_Standard_Time=>T("TimezoneAbbriviations.Chatham Islands Standard Time"); +public static string TimezoneAbbreviations_Chatham_Islands_Standard_Time=>T("TimezoneAbbreviations.Chatham Islands Standard Time"); ///"TOT" -public static string TimezoneAbbriviations_Tonga_Standard_Time=>T("TimezoneAbbriviations.Tonga Standard Time"); +public static string TimezoneAbbreviations_Tonga_Standard_Time=>T("TimezoneAbbreviations.Tonga Standard Time"); ///"WSDT" -public static string TimezoneAbbriviations_Samoa_Standard_Time=>T("TimezoneAbbriviations.Samoa Standard Time"); +public static string TimezoneAbbreviations_Samoa_Standard_Time=>T("TimezoneAbbreviations.Samoa Standard Time"); ///"LINT" -public static string TimezoneAbbriviations_Line_Islands_Standard_Time=>T("TimezoneAbbriviations.Line Islands Standard Time"); +public static string TimezoneAbbreviations_Line_Islands_Standard_Time=>T("TimezoneAbbreviations.Line Islands Standard Time"); private static string T(string key) { - return StringResourceSystemFacade.GetString("Composite.Plugins.TimezoneAbbriviations", key); + return StringResourceSystemFacade.GetString("Composite.Plugins.TimezoneAbbreviations", key); } } diff --git a/Composite/Data/Types/IVersionedDataHelper.cs b/Composite/Data/Types/IVersionedDataHelper.cs index 0acb4b03fe..6fd90e14d0 100644 --- a/Composite/Data/Types/IVersionedDataHelper.cs +++ b/Composite/Data/Types/IVersionedDataHelper.cs @@ -38,12 +38,16 @@ public static void RegisterVersionHelper(VersionedPageHelperContract vpc) /// public static string LocalizedVersionName(this T str) where T : IVersioned { + var defaultVersionName = + Core.ResourceSystem.LocalizationFiles.Composite_Management.DefaultVersionName; + if (_instances == null) { - return ""; + return defaultVersionName; } - return _instances.Select(p => p.LocalizedVersionName(str)).Single(name => name != null); + return _instances.Select(p => p.LocalizedVersionName(str)).Any(name => name != null)? + string.Join(",", _instances.Select(p => p.LocalizedVersionName(str)).Where(name => name != null)): defaultVersionName; } /// diff --git a/Website/Composite/localization/Composite.Management.en-us.xml b/Website/Composite/localization/Composite.Management.en-us.xml index 11c80e1683..93e1a4e780 100644 --- a/Website/Composite/localization/Composite.Management.en-us.xml +++ b/Website/Composite/localization/Composite.Management.en-us.xml @@ -681,4 +681,6 @@ + + From 4512696176a3810b65312a6a15a23a2ee5233d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Tue, 9 Aug 2016 08:38:52 +0200 Subject: [PATCH 178/213] Made some flaky tests more robust. --- Website/test/e2e/pageObjects/login.js | 2 +- Website/test/e2e/pageObjects/startScreen.js | 2 +- Website/test/e2e/reset.js | 6 ++---- nightwatch.json | 6 +++--- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Website/test/e2e/pageObjects/login.js b/Website/test/e2e/pageObjects/login.js index a0c2555b8c..3a89421312 100644 --- a/Website/test/e2e/pageObjects/login.js +++ b/Website/test/e2e/pageObjects/login.js @@ -10,7 +10,7 @@ module.exports = { commands: [ { isShown: function () { - this.waitForElementVisible('@usernameField', 1500); + this.waitForElementVisible('@usernameField', 2500); return this; }, setUsername: function (username) { diff --git a/Website/test/e2e/pageObjects/startScreen.js b/Website/test/e2e/pageObjects/startScreen.js index 57b0428fcb..e3b16a9ef4 100644 --- a/Website/test/e2e/pageObjects/startScreen.js +++ b/Website/test/e2e/pageObjects/startScreen.js @@ -12,7 +12,7 @@ module.exports = { }, enter: function () { this.api.page.appWindow().enter(); // Start page shows inside appwindow. - this.waitForElementPresent('@startFrame', 1000); + this.waitForElementPresent('@startFrame', 3000); this.api.pause(1000); this.enterFrame('@startFrame'); // Enter the frame containing it return this; diff --git a/Website/test/e2e/reset.js b/Website/test/e2e/reset.js index 72b9043ba5..18b9863419 100644 --- a/Website/test/e2e/reset.js +++ b/Website/test/e2e/reset.js @@ -51,9 +51,7 @@ const removePaths = [ 'Website/bin/CookComputing.XmlRpcV2.dll', 'Website/bin/ICSharpCode.SharpZipLib.dll', 'Website/bin/System.Web.Optimization.dll', - 'Website/bin/WebGrease.dll', - 'Website/Web.config', - 'Website/App_Data/Composite/Composite.config' + 'Website/bin/WebGrease.dll' ] const rimraf = require('rimraf'); const fs = require('fs'); @@ -64,7 +62,7 @@ function copyFile(source, target) { return new Promise(function(resolve, reject) { var reader = fs.createReadStream(source); reader.on('error', reject); - var writer = fs.createWriteStream(target); + var writer = fs.createWriteStream(target, { flags: 'r+' }); writer.on('error', reject); writer.on('finish', resolve); reader.pipe(writer); diff --git a/nightwatch.json b/nightwatch.json index c01decacda..cc1c046068 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -2,7 +2,7 @@ "src_folders" : [ "Website/test/e2e/suite" ], - "output_folder" : "Website/test/e2e/reports", + "output_folder" : "Website/test/e2e/reports", "custom_commands_path" : "Website/test/e2e/commands", "custom_assertions_path" : "", "page_objects_path" : "Website/test/e2e/pageObjects", @@ -26,8 +26,8 @@ "selenium_port" : 4444, "selenium_host" : "localhost", "silent": true, - "end_session_on_fail": false, - "skip_testcases_on_fail": false, + "end_session_on_fail": false, + "skip_testcases_on_fail": false, "screenshots" : { "enabled" : false, "path" : "" From bca1fa5cfd165169e312e016eb49bfb33ce8e422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Tue, 9 Aug 2016 10:06:28 +0200 Subject: [PATCH 179/213] Site reset more robust. Install test tagged. --- Website/test/e2e/reset.js | 7 +++++-- Website/test/e2e/suite/000_setup/installSite.js | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Website/test/e2e/reset.js b/Website/test/e2e/reset.js index 18b9863419..47cf7c50c3 100644 --- a/Website/test/e2e/reset.js +++ b/Website/test/e2e/reset.js @@ -62,10 +62,13 @@ function copyFile(source, target) { return new Promise(function(resolve, reject) { var reader = fs.createReadStream(source); reader.on('error', reject); - var writer = fs.createWriteStream(target, { flags: 'r+' }); + var id = Math.ceil(10000 * Math.random()); + var writer = fs.createWriteStream('tmp' + id); writer.on('error', reject); - writer.on('finish', resolve); + writer.on('finish', function () { resolve(id); }); reader.pipe(writer); + }).then(function (id) { + fs.renameSync('tmp' + id, target); }); } diff --git a/Website/test/e2e/suite/000_setup/installSite.js b/Website/test/e2e/suite/000_setup/installSite.js index 86179f6c4b..0873c82ce5 100644 --- a/Website/test/e2e/suite/000_setup/installSite.js +++ b/Website/test/e2e/suite/000_setup/installSite.js @@ -3,6 +3,7 @@ var resetSite = require('../../reset.js'); var expectedLanguage = 'en-US'; module.exports = { + '@tags': ['install'], before: function (_, done) { resetSite(function (err) { if (err) { From 9b5a0f925a44b3bb246600854946d1aaa6013fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Tue, 9 Aug 2016 10:46:07 +0200 Subject: [PATCH 180/213] Lengthened some timeouts to keep editor loading from causing failures. --- Website/test/e2e/pageObjects/editor.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Website/test/e2e/pageObjects/editor.js b/Website/test/e2e/pageObjects/editor.js index 3974fe8f53..a2b7090e9d 100644 --- a/Website/test/e2e/pageObjects/editor.js +++ b/Website/test/e2e/pageObjects/editor.js @@ -13,12 +13,12 @@ module.exports = { { enter: function () { this - .waitForElementVisible('@editorFrame', 2000) - .waitForFrameLoad('@editorFrame', 2000) + .waitForElementVisible('@editorFrame', 3000) + .waitForFrameLoad('@editorFrame', 3000) .enterFrame('@editorFrame') - .waitForFrameLoad('iframe[src^="tinymce.aspx"]', 3000) + .waitForFrameLoad('iframe[src^="tinymce.aspx"]', 10000) // Potentially v. slow .enterFrame('iframe[src^="tinymce.aspx"]') - .waitForFrameLoad('#editor_ifr', 2000) + .waitForFrameLoad('#editor_ifr', 3000) .enterFrame('#editor_ifr') return this; }, From 65ba9c67ae971872db9ccd8602280926e8e822e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Tue, 9 Aug 2016 11:39:52 +0200 Subject: [PATCH 181/213] Added rimraf module. --- Website/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Website/package.json b/Website/package.json index ee53733315..02ae1f25b1 100644 --- a/Website/package.json +++ b/Website/package.json @@ -15,6 +15,7 @@ "iedriver": "2.53.1", "less": "^2.1.2", "nightwatch": "0.9.3", + "rimraf": "2.5.4", "selenium-server-standalone-jar": "2.53.1", "xml2js": "^0.4.10" } From a743447daa6bdbff710ec9c25cbad1e8e97e96db Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 10 Aug 2016 13:00:30 +0200 Subject: [PATCH 182/213] Removing ITimedPublishing interface --- Composite/Composite.csproj | 1 - Composite/Data/Types/IPage.cs | 2 +- Composite/Data/Types/ITimedPublishing.cs | 29 ------------------------ Composite/Data/Types/IVersioned.cs | 2 +- 4 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 Composite/Data/Types/ITimedPublishing.cs diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index d4404a6766..aa2cc2f649 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -177,7 +177,6 @@ - diff --git a/Composite/Data/Types/IPage.cs b/Composite/Data/Types/IPage.cs index 37cbc76767..a9613af014 100644 --- a/Composite/Data/Types/IPage.cs +++ b/Composite/Data/Types/IPage.cs @@ -28,7 +28,7 @@ namespace Composite.Data.Types [PublishControlledAuxiliary(typeof(PagePublishControlledAuxiliary))] [PublishProcessControllerTypeAttribute(typeof(GenericPublishProcessController))] [KeyTemplatedXhtmlRenderer(XhtmlRenderingType.Embedable, "{label}")] - public interface IPage : IData, IChangeHistory, ICreationHistory, IPublishControlled, ILocalizedControlled, ITimedPublishing + public interface IPage : IData, IChangeHistory, ICreationHistory, IPublishControlled, ILocalizedControlled, IVersioned { /// [StoreFieldType(PhysicalStoreFieldType.Guid)] diff --git a/Composite/Data/Types/ITimedPublishing.cs b/Composite/Data/Types/ITimedPublishing.cs deleted file mode 100644 index 6a9534fc99..0000000000 --- a/Composite/Data/Types/ITimedPublishing.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace Composite.Data.Types -{ - /// - /// Represents a data item with timed publishing - /// - public interface ITimedPublishing: IVersioned, IData - { - /// - /// Time from which the current version of the data item will be visible - /// - [StoreFieldType(PhysicalStoreFieldType.DateTime, IsNullable = true)] - [ImmutableFieldId("{d70973a4-3b57-49b4-81ed-34ae92d8637f}")] - DateTime? PublishTime { get; set; } - - /// - /// Time until which the current version of the data item will be visible - /// - [StoreFieldType(PhysicalStoreFieldType.DateTime, IsNullable = true)] - [ImmutableFieldId("{806d1edc-b3f2-4cb4-b18f-199cca9ee2df}")] - DateTime? UnpublishTime { get; set; } - - /// - [StoreFieldType(PhysicalStoreFieldType.String, 128, IsNullable = true)] - [ImmutableFieldId("{70b53ac6-d58e-4624-ab47-975e80853673}")] - string VersionName { get; set; } - } -} diff --git a/Composite/Data/Types/IVersioned.cs b/Composite/Data/Types/IVersioned.cs index c5d98c6625..f5dd0a9699 100644 --- a/Composite/Data/Types/IVersioned.cs +++ b/Composite/Data/Types/IVersioned.cs @@ -3,7 +3,7 @@ namespace Composite.Data.Types { /// - /// Represends a data type that supports multiple versions of the same data type + /// Represents a data type that supports multiple versions of the same data item /// [VersionKeyPropertyName(nameof(VersionId))] public interface IVersioned : IData From 39d9384afc3ecc3eb8631d1de80d53aecb0e4636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Wed, 10 Aug 2016 16:34:46 +0200 Subject: [PATCH 183/213] Adapted tests to run on Chrome instead of Firefox. Editor tests still fail. --- Website/test/e2e/globals.js | 3 +++ Website/test/e2e/pageObjects/editor.js | 1 + Website/test/e2e/suite/000_setup/installSite.js | 8 ++++++++ Website/test/e2e/suite/200_startScreen/logged-in.js | 2 +- nightwatch.json | 6 +++--- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Website/test/e2e/globals.js b/Website/test/e2e/globals.js index 97f1527988..58226915f4 100644 --- a/Website/test/e2e/globals.js +++ b/Website/test/e2e/globals.js @@ -1,4 +1,7 @@ module.exports = { + beforeEach: function (browser, done) { + browser.resizeWindow(1600, 1000, done); + }, afterEach: function (browser, done) { browser.end(done); } diff --git a/Website/test/e2e/pageObjects/editor.js b/Website/test/e2e/pageObjects/editor.js index a2b7090e9d..bb81ecb001 100644 --- a/Website/test/e2e/pageObjects/editor.js +++ b/Website/test/e2e/pageObjects/editor.js @@ -37,6 +37,7 @@ module.exports = { }, save: function () { this.selectFrame('#savebutton') + this.api.pause(500); this.expect.element('#savebutton').to.not.have.attribute('isdisabled'); this.click('#savebutton > labelbox'); this.api.pause(1000) diff --git a/Website/test/e2e/suite/000_setup/installSite.js b/Website/test/e2e/suite/000_setup/installSite.js index 0873c82ce5..23f75b7dab 100644 --- a/Website/test/e2e/suite/000_setup/installSite.js +++ b/Website/test/e2e/suite/000_setup/installSite.js @@ -80,15 +80,20 @@ module.exports = { // The “Regional settings” field is set to the same language as in Step 5. browser.expect.element('#consolelanguage').value.to.equal(expectedLanguage); // 7 Fill the “Email” field. Value: “john.doe@contoso.com” + browser.click('#email'); browser.setValue('#email', 'john.doe@contoso.com'); // The field is filled with the value. + browser.expect.element('#email').value.to.equal('john.doe@contoso.com') // The “Start C1” button is still disabled. browser.expect .element('#navlogin clickbutton[label="Start C1"]') .to.have.attribute('isdisabled'); // 8 Fill the “Password” field. Value: “123456” + browser.click('#password'); browser.setValue('#password', '123456') // The field is filled with the value. + browser.expect.element('#password') + .value.to.equal('123456'); // The value is masked. browser.expect.element('#password') .to.have.attribute('type', 'password') @@ -97,8 +102,11 @@ module.exports = { .element('#navlogin clickbutton[label="Start C1"]') .to.have.attribute('isdisabled'); // 9 Fill the “Repeat Password” field. Value: “123456” + browser.click('#passcheck'); browser.setValue('#passcheck', '123456') // The field is filled with the value. + browser.expect.element('#passcheck') + .value.to.equal('123456'); // The value is masked. browser.expect.element('#passcheck') .to.have.attribute('type', 'password') diff --git a/Website/test/e2e/suite/200_startScreen/logged-in.js b/Website/test/e2e/suite/200_startScreen/logged-in.js index 07c3c2b788..b558522fe6 100644 --- a/Website/test/e2e/suite/200_startScreen/logged-in.js +++ b/Website/test/e2e/suite/200_startScreen/logged-in.js @@ -5,6 +5,6 @@ module.exports = { browser.refresh(); browser.page.appWindow().enter(); browser.page.startScreen() - .assert.elementNotPresent('@startFrame'); + .assert.hidden('@startFrame'); } }; diff --git a/nightwatch.json b/nightwatch.json index cc1c046068..9dbcffb7cb 100644 --- a/nightwatch.json +++ b/nightwatch.json @@ -33,16 +33,16 @@ "path" : "" }, "desiredCapabilities": { - "browserName": "firefox", + "browserName": "chrome", "loggingPrefs": { "browser": "ALL" } , "javascriptEnabled": true, "acceptSslCerts": true } }, - "chrome" : { + "firefox" : { "desiredCapabilities": { - "browserName": "chrome", + "browserName": "firefox", "loggingPrefs": { "browser": "ALL" } , "javascriptEnabled": true, "acceptSslCerts": true From a6685da06082ba909dd79f8e6c7126417e3f58e4 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 11 Aug 2016 10:50:24 +0200 Subject: [PATCH 184/213] DataFacade - deleting a data item should not trigger cascade deletion if some other versions of the data item will remain --- Composite/Data/DataFacadeImpl.cs | 56 +++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/Composite/Data/DataFacadeImpl.cs b/Composite/Data/DataFacadeImpl.cs index 4070aca6d9..f0206692ef 100644 --- a/Composite/Data/DataFacadeImpl.cs +++ b/Composite/Data/DataFacadeImpl.cs @@ -460,7 +460,7 @@ public void Delete(IEnumerable dataset, bool suppressEventing, CascadeDele private void Delete(IEnumerable dataset, bool suppressEventing, CascadeDeleteType cascadeDeleteType, bool referencesFromAllScopes, HashSet dataPendingDeletion) where T : class, IData { - Verify.ArgumentNotNull(dataset, "dataset"); + Verify.ArgumentNotNull(dataset, nameof(dataset)); dataset = dataset.Evaluate(); @@ -477,32 +477,50 @@ private void Delete(IEnumerable dataset, bool suppressEventing, CascadeDel { foreach (IData element in dataset) { - Verify.ArgumentCondition(element != null, "dataset", "datas may not contain nulls"); + Verify.ArgumentCondition(element != null, nameof(dataset), "dataset may not contain nulls"); - if (element.IsDataReferred()) - { - Verify.IsTrue(cascadeDeleteType != CascadeDeleteType.Disallow, "One of the given datas is referenced by one or more datas"); + if (!element.IsDataReferred()) continue; - element.RemoveOptionalReferences(); + Type interfaceType = element.DataSourceId.InterfaceType; + + // Not deleting references if the data is versioned and not all of the + // versions of the element are to be deleted + if (element is IVersioned) + { + var key = element.GetUniqueKey(); + var versions = DataFacade.TryGetDataVersionsByUniqueKey(interfaceType, key).ToList(); - IEnumerable referees; - using (new DataScope(element.DataSourceId.DataScopeIdentifier)) + if (versions.Count > 1 + && (dataset.Count() < versions.Count + || !versions.All(v => dataPendingDeletion.Contains(v.DataSourceId)))) { - // For some wierd reason, this line does not work.... /MRJ - // IEnumerable referees = dataset.GetRefereesRecursively(); - referees = element.GetReferees(referencesFromAllScopes).Where(reference => !dataPendingDeletion.Contains(reference.DataSourceId)); + continue; } + } + + Verify.IsTrue(cascadeDeleteType != CascadeDeleteType.Disallow, "One of the given datas is referenced by one or more datas"); - foreach (IData referee in referees) + element.RemoveOptionalReferences(); + + IEnumerable referees; + using (new DataScope(element.DataSourceId.DataScopeIdentifier)) + { + // For some weird reason, this line does not work.... /MRJ + // IEnumerable referees = dataset.GetRefereesRecursively(); + referees = element.GetReferees(referencesFromAllScopes) + .Where(reference => !dataPendingDeletion.Contains(reference.DataSourceId)) + .Evaluate(); + } + + foreach (IData referee in referees) + { + if (!referee.CascadeDeleteAllowed(interfaceType)) { - if (referee.CascadeDeleteAllowed(element.DataSourceId.InterfaceType) == false) - { - throw new InvalidOperationException("One of the given datas is referenced by one or more datas that does not allow cascade delete"); - } + throw new InvalidOperationException("One of the given datas is referenced by one or more datas that does not allow cascade delete"); } - - Delete(referees, suppressEventing, cascadeDeleteType, referencesFromAllScopes); } + + Delete(referees, suppressEventing, cascadeDeleteType, referencesFromAllScopes); } } @@ -523,7 +541,7 @@ private void Delete(IEnumerable dataset, bool suppressEventing, CascadeDel } - if (suppressEventing == false) + if (!suppressEventing) { foreach (IData element in dataset) { From 84253b92cd2469d638793aef76ffa0849fb006ba Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 11 Aug 2016 15:30:44 +0200 Subject: [PATCH 185/213] List unpublished items - fixing the date styling --- .../publishworkflowstatus/bindings/UnpublishedPageBinding.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js b/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js index 27736a2df9..3aa79ef93c 100644 --- a/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js +++ b/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js @@ -189,12 +189,10 @@ UnpublishedPageBinding.prototype.renderTable = function (nodes, selected) { this.addTextCell(row, node.getPropertyBag().PublishDate, { "data-sort-value": node.getPropertyBag().SortablePublishDate , - "class": "date" } ); this.addTextCell(row, node.getPropertyBag().UnpublishDate, { "data-sort-value": node.getPropertyBag().SortableUnpublishDate, - "class": "date" }); this.addTextCell(row, node.getPropertyBag().Created).setAttribute("data-sort-value", node.getPropertyBag().SortableCreated); this.addTextCell(row, node.getPropertyBag().Modified).setAttribute("data-sort-value", node.getPropertyBag().SortableModified); From 82e18186ae3a248b2e9b7fe926c323168a9c131a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 12 Aug 2016 09:27:06 +0200 Subject: [PATCH 186/213] Completely rebuilt selectFrame command. Now works anywhere that Nightwatch does. --- Website/test/e2e/commands/selectFrame.js | 139 +++++++++++++----- Website/test/e2e/globals.js | 2 +- Website/test/e2e/pageObjects/editor.js | 3 +- .../test/e2e/suite/400_content/multiEditor.js | 7 +- .../e2e/suite/400_content/singleEditor.js | 6 +- 5 files changed, 113 insertions(+), 44 deletions(-) diff --git a/Website/test/e2e/commands/selectFrame.js b/Website/test/e2e/commands/selectFrame.js index ad14801c84..d13b86a331 100644 --- a/Website/test/e2e/commands/selectFrame.js +++ b/Website/test/e2e/commands/selectFrame.js @@ -2,47 +2,118 @@ var util = require('util'); var events = require('events'); function SelectFrame() { - events.EventEmitter.call(this); + events.EventEmitter.call(this); } util.inherits(SelectFrame, events.EventEmitter); -SelectFrame.prototype.command = function(selector, noReset) { - if (!noReset) { - this.client.api.topFrame() - } - this.client.api - .execute(function (selector) { - function checkFrame(frame) { - var node = frame.document.querySelector(selector); - if (node) { - return []; - } else { - for (var i = 0; i < frame.length; i += 1) { - try { - var frameResult = checkFrame(frame[i]); - if (frameResult) { - frameResult.unshift(i); - return frameResult; - } - } catch (_) {} - } - return null; - } +function elementPresentPromise(client, selector) { + return new Promise(resolve => { + client.element('css selector', selector, result => { + if (result.error) { + resolve(false); + } else { + resolve(true); + } + }); + }); +} + +function elementListPromise(client, selector) { + return new Promise((resolve, reject) => { + client.elements('css selector', selector, result => { + if (result.error) { + reject(result.error); + } else { + resolve(result.value); } - return checkFrame(window); - }, - [selector], - result => { - if (Array.isArray(result.value)) { - result.value.forEach(key => this.client.api.frame(key)); - // this.client.assertion(true, null, null, 'Found element <' + selector + '> and entered frame containing it' + (noReset ? ' without resetting to top frame' : ''), this.abortOnFailure); + }); + }); +} + +function framePromise(client, element) { + return new Promise((resolve, reject) => { + client.frame(element, result => { + if (result.error) { + reject(result.error); } else { - this.client.assertion(false, null, null, 'Did not find selector <' + selector + '> in any frame.', this.abortOnFailure, this._stackTrace); + resolve(); } - this.emit('complete'); }); - return this.client.api; -}; + }); +} + +function frameParentPromise(client) { + return new Promise((resolve, reject) => { + client.frameParent(result => { + if (result.error) { + reject(result.error); + } else { + resolve(); + } + }); + }); +} + +function promiseSome(list, iterator) { + var p = Promise.resolve(false); + list.forEach((item, index) => { + p = p.then(result => { + if (result) { + return true; + } else { + return iterator(item); + } + }) + }); + return p; +} + +function findSelectorInsideFrame(client, selector) { + return elementPresentPromise(client, selector).then(found => { + return !!found || + elementListPromise(client, 'iframe') + .then(frames => promiseSome(frames, frame => + framePromise(client, frame) + .then(() => { + return findSelectorInsideFrame(client, selector); + }) + .then(result => { + if (result) { + return true; + } else { + return frameParentPromise(client).then(Promise.resolve(false)); + } + }) + .catch(err => { + // if (err) { + // console.error('Enter frame failed:', err); + // } + return false; + }) + )) + }); +} + +SelectFrame.prototype.command = function(selector, noReset) { + var reset = noReset ? Promise.resolve() : framePromise(this.client.api, null); + reset + .then(() => findSelectorInsideFrame(this.client.api, selector)) + .then(found => { + if (!found) { + this.client.assertion(false, 'not found', 'found', 'Did not find selector <' + selector + '> in any frame.', this.abortOnFailure, this._stackTrace); + // } else { + // this.client.assertion(true, null, null, 'Found element <' + selector + '> and entered frame containing it' + + // (noReset + // ? ' without resetting to top frame' + // : ''), this.abortOnFailure); + } + this.emit('complete'); + }) + .catch(err => { + this.client.assertion(false, err, null, 'Attempt to find <' + selector + '> failed horribly'); + this.emit('complete'); + }); +} module.exports = SelectFrame; diff --git a/Website/test/e2e/globals.js b/Website/test/e2e/globals.js index 58226915f4..2ace14c455 100644 --- a/Website/test/e2e/globals.js +++ b/Website/test/e2e/globals.js @@ -1,6 +1,6 @@ module.exports = { beforeEach: function (browser, done) { - browser.resizeWindow(1600, 1000, done); + browser.resizeWindow(1400, 1000, done); }, afterEach: function (browser, done) { browser.end(done); diff --git a/Website/test/e2e/pageObjects/editor.js b/Website/test/e2e/pageObjects/editor.js index bb81ecb001..336e60d060 100644 --- a/Website/test/e2e/pageObjects/editor.js +++ b/Website/test/e2e/pageObjects/editor.js @@ -36,8 +36,7 @@ module.exports = { return this; }, save: function () { - this.selectFrame('#savebutton') - this.api.pause(500); + this.selectFrame('#savebutton'); this.expect.element('#savebutton').to.not.have.attribute('isdisabled'); this.click('#savebutton > labelbox'); this.api.pause(1000) diff --git a/Website/test/e2e/suite/400_content/multiEditor.js b/Website/test/e2e/suite/400_content/multiEditor.js index 0913bb4415..22f7d0d498 100644 --- a/Website/test/e2e/suite/400_content/multiEditor.js +++ b/Website/test/e2e/suite/400_content/multiEditor.js @@ -53,8 +53,7 @@ module.exports = { browser .clickInFrame('#renderingdialogpage', 'clickbutton[callbackid="buttonAccept"]', 1000); // Save change. - editor - .save() + editor.save() // Close editor after you content .enter() // Reset to content frame @@ -69,8 +68,8 @@ module.exports = { content .enter() .enterFrame('@browserFrame') - .click('#moreactionsbutton') - .click('menuitem[image="item-undo-unpublished-changes"]') + // .click('#moreactionsbutton') + .click('toolbarbutton[image="item-undo-unpublished-changes"]') .click('toolbarbutton[image="item-publish"]', done); } }; diff --git a/Website/test/e2e/suite/400_content/singleEditor.js b/Website/test/e2e/suite/400_content/singleEditor.js index ae494b20c5..a06507f868 100644 --- a/Website/test/e2e/suite/400_content/singleEditor.js +++ b/Website/test/e2e/suite/400_content/singleEditor.js @@ -30,7 +30,7 @@ module.exports = { .changeElementContent('h1', 'Moving forward') browser.pause(100); // Save change. - editor + editor .save() content // Close editor after you @@ -46,8 +46,8 @@ module.exports = { content .enter() .enterFrame('@browserFrame') - .click('#moreactionsbutton') - .click('menuitem[image="item-undo-unpublished-changes"]') + // .click('#moreactionsbutton') + .click('toolbarbutton[image="item-undo-unpublished-changes"]') .click('toolbarbutton[image="item-publish"]', done); } } From 7e5a50d6cefbbfc60613886e68a6b3b31c1ad53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 12 Aug 2016 10:09:31 +0200 Subject: [PATCH 187/213] Fixed it so in case of error when resetting site for install test, browser also gets shut down correctly. --- Website/test/e2e/suite/000_setup/installSite.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Website/test/e2e/suite/000_setup/installSite.js b/Website/test/e2e/suite/000_setup/installSite.js index 23f75b7dab..7074c173ba 100644 --- a/Website/test/e2e/suite/000_setup/installSite.js +++ b/Website/test/e2e/suite/000_setup/installSite.js @@ -4,9 +4,10 @@ var expectedLanguage = 'en-US'; module.exports = { '@tags': ['install'], - before: function (_, done) { + before: function (browser, done) { resetSite(function (err) { if (err) { + browser.end(); console.error(err); process.exit(1); } From 1c5959c9c1729045b1aa027f5189d4f79db53166 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 12 Aug 2016 10:36:01 +0200 Subject: [PATCH 188/213] Removing "sentToDraft" page publication state --- .../GenericPublishProcessController.cs | 16 +++++++--------- .../PageElementProvider/PageElementProvider.cs | 2 +- .../localization/Composite.Management.en-us.xml | 1 - .../Composite/services/Tree/TreeServices.asmx | 1 - 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs b/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs index 7c0d440270..c72f2dd4f3 100644 --- a/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs +++ b/Composite/Data/ProcessControlled/ProcessControllers/GenericPublishProcessController/GenericPublishProcessController.cs @@ -42,9 +42,6 @@ public sealed class GenericPublishProcessController : IPublishProcessController /// public const string Published = "published"; - /// - public const string SentToDraft = "sentToDraft"; - public static string BulkPublishingCommandsTag { get; } = "BulkPublishingCommands"; private static readonly string _backToAwaitingApproval = "awaitingApprovalBack"; @@ -99,7 +96,6 @@ public GenericPublishProcessController() _transitions = new Dictionary> { {Draft, new List {AwaitingApproval, AwaitingPublication, Published}}, - {SentToDraft, new List {AwaitingApproval, AwaitingPublication, Published}}, {AwaitingApproval, new List {Draft, AwaitingPublication, Published}}, {AwaitingPublication, new List {Draft, AwaitingApproval, Published}}, {Published, new List {Draft, AwaitingApproval, AwaitingPublication}} @@ -108,7 +104,6 @@ public GenericPublishProcessController() _visualTransitions = new Dictionary> { {Draft, new List { _draftDisabled, _forwardToAwaitingApproval, _forwardToAwaitingPublication, Published }}, - {SentToDraft, new List { _draftDisabled, _forwardToAwaitingApproval, _forwardToAwaitingPublication, Published }}, {AwaitingApproval, new List { Draft, _awaitingApprovalDisabled, _forwardToAwaitingPublication, Published }}, {AwaitingPublication, new List { Draft, _backToAwaitingApproval, _awaitingPublicationDisabled, Published }}, {Published, new List()} // when public, no "send to" available. @@ -117,7 +112,6 @@ public GenericPublishProcessController() _transitionNames = new Dictionary { {Draft, ManagementStrings.PublishingStatus_draft}, - {SentToDraft, ManagementStrings.PublishingStatus_sentToDraft}, {AwaitingApproval, ManagementStrings.PublishingStatus_awaitingApproval}, {AwaitingPublication, ManagementStrings.PublishingStatus_awaitingPublication}, {Published, ManagementStrings.PublishingStatus_published} @@ -366,7 +360,11 @@ public List GetActions(IData data, Type elementProviderType) var publishControlled = (IPublishControlled)data; - IList visualTrans = _visualTransitions[publishControlled.PublicationStatus]; + IList visualTrans; + if (!_visualTransitions.TryGetValue(publishControlled.PublicationStatus, out visualTrans)) + { + throw new InvalidOperationException($"Unknown publication state '{publishControlled.PublicationStatus}'"); + } var clientActions = visualTrans.Select(newState => _visualTransitionsActions[newState]()).ToList(); @@ -396,7 +394,7 @@ public List GetActions(IData data, Type elementProviderType) - if (publishControlled.PublicationStatus == Draft || publishControlled.PublicationStatus == SentToDraft) + if (publishControlled.PublicationStatus == Draft) { if (ProcessControllerAttributesFacade.IsActionIgnored(elementProviderType, GenericPublishProcessControllerActionTypeNames.UndoUnpublishedChanges) == false) { @@ -571,7 +569,7 @@ public FlowToken Execute(EntityToken entityToken, ActionToken actionToken, FlowC } else if (actionToken is DraftActionToken) { - publishControlled.PublicationStatus = SentToDraft; + publishControlled.PublicationStatus = Draft; } else if (actionToken is AwaitingApprovalActionToken) { diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index 5ed3d3eedc..c291dce342 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -823,7 +823,7 @@ private ElementVisualizedData MakeVisualData(IPage page, PageLocaleState pageLoc if (pageLocaleState == PageLocaleState.Own) { - if ((page.PublicationStatus == GenericPublishProcessController.Draft)|| (page.PublicationStatus == GenericPublishProcessController.SentToDraft)) + if (page.PublicationStatus == GenericPublishProcessController.Draft) { visualizedElement.Icon = PageElementProvider.PageDraft; visualizedElement.OpenedIcon = PageElementProvider.PageDraft; diff --git a/Website/Composite/localization/Composite.Management.en-us.xml b/Website/Composite/localization/Composite.Management.en-us.xml index 93e1a4e780..3ab892ee69 100644 --- a/Website/Composite/localization/Composite.Management.en-us.xml +++ b/Website/Composite/localization/Composite.Management.en-us.xml @@ -22,7 +22,6 @@ - diff --git a/Website/Composite/services/Tree/TreeServices.asmx b/Website/Composite/services/Tree/TreeServices.asmx index 5c8edeb583..6f6737773b 100644 --- a/Website/Composite/services/Tree/TreeServices.asmx +++ b/Website/Composite/services/Tree/TreeServices.asmx @@ -94,7 +94,6 @@ namespace Composite.Services {GenericPublishProcessController.Draft, Texts.PublishingStatus_draft}, {GenericPublishProcessController.AwaitingApproval, Texts.PublishingStatus_awaitingApproval}, {GenericPublishProcessController.AwaitingPublication, Texts.PublishingStatus_awaitingPublication}, - {GenericPublishProcessController.SentToDraft, Texts.PublishingStatus_sentToDraft} }; List> actionRequiredPages = From 3604c473b1a8bb43ad1980f3d733534e74326325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 12 Aug 2016 12:02:14 +0200 Subject: [PATCH 189/213] Added readme file for e2e testing --- Website/test/e2e/README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Website/test/e2e/README.md diff --git a/Website/test/e2e/README.md b/Website/test/e2e/README.md new file mode 100644 index 0000000000..de77fef16f --- /dev/null +++ b/Website/test/e2e/README.md @@ -0,0 +1,37 @@ +# Warnings + +This test suite is intended to verify console function when changing functionality within the C1 CMS. **Do not run it** to test any particular site setup as it will not only not work, but will delete data installed on this C1 copy. + +# Running the nightwatch tests + +In order to run the end-to-end test suite, you will first need to have built your working copy of C1 with Visual Studio, installed all npm packages, and run the grunt build task as documented elsewhere. + +In order to install nightwatch, you will need to access your command line. This can be done by either starting PowerShell or the Windows Command Line utility, `cmd`. + +First we need to verify that you have the needed version of node.js, which is used to execute the tests. In your command line, enter the command `node -v`. This should respond with better than version 4.4. If your version is older (or you don't have node.js installed), you can install the current version from [the official website](https://nodejs.org/en/download/). As of this writing, the recommended version is 4.4.7 LTS (subject to change). + +The next step is installing nightwatch itself. This is done by issuing the command `npm install -g nightwatch`. This will install nightwatch as a globally accessible command. There may be some warnings issued by npm as it runs, these can usually be ignored without issue. + +Finally, navigate to the root directory of your C1 working copy. This will usually be named `C1-CMS`, but you may have named it otherwise when cloning it. In this directory, you can then start the tests by running `nightwatch` from your command line. This will run the whole test suite, starting with installing the Venus starter site. + +Due to certain technical limitations, nightwatch must always be run from the working directory, and cannot be run from any subdirectory. + +## Running tests separately + +A few methods exist to run specific tests. If you wish to rerun the installation test alone, you can exploit the fact that it has the `install` tag. If you add the 'tag' command line switch - `nightwatch --tag install` - only tests with that tag will be run, no others. + +If, conversely, you wish to run all other tests, but not the install test - for instance if you just ran the install test separately and it passed - you can skip that test by using `nightwatch --skiptags install`. + +Lastly, if you wish to run a particular test, locate the file containing it, and simply give that as a parameter to the nightwatch command, like this: `nightwatch .\Website\test\e2e\100_login\devMode.js`. This also works if you wish to run the tests in a specific directory: `nightwatch .\Website\test\e2e\100_login\` will run all tests in that directory. + +## Constructing new tests + +Test related files are found within `.\Website\test\e2e\`, alongside this readme file. + +To build new tests, it is recommended to look at what already exists, and structure your tests similarly. It is also recommended to familiarize yourself with nightwatch's own [documentation](http://nightwatchjs.org/guide). + +A test file consists of a CommonJS formatted module, exporting a single object with functions as members. A few names on these objects are reserved - `before`, `after`, `beforeEach` and `afterEach` - for setting up and tearing down your tests. You can look this up in [the nightwatch developer guide](http://nightwatchjs.org/guide#using-before-each-and-after-each-hooks) for the full details. Any other member of that object will be run as a test case. + +Custom commands have been added to accommodate testing within the specific system of C1's console. These may be found within the `commands` directory, with examples of use found throughout the test cases. + +A number of [page objects](http://nightwatchjs.org/guide#page-objects) have been constructed as well, providing ease of access, custom commands and assertions specific to certain parts of the console. These may be found in the `pageObjects` directory, again, with examples of use throughout the tests. From 366e4076e7f39705e614898d4e40b7b9b9619bc8 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 12 Aug 2016 14:33:11 +0200 Subject: [PATCH 190/213] Fix: deleting a page version would leave a public copy --- Composite/Data/Types/PageServices.cs | 46 ++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/Composite/Data/Types/PageServices.cs b/Composite/Data/Types/PageServices.cs index 21d2da1526..c1304f40cd 100644 --- a/Composite/Data/Types/PageServices.cs +++ b/Composite/Data/Types/PageServices.cs @@ -557,26 +557,52 @@ private static void RemoveAllFolderAndMetaDataDefinitions(IPage page) /// /// /// + /// public static void DeletePage(Guid pageId, Guid versionId, CultureInfo locale) { - using (var conn = new DataConnection(locale)) - using (var scope = TransactionsFacade.CreateNewScope()) + Verify.ArgumentNotNull(locale, nameof(locale)); + + using (var conn = new DataConnection(PublicationScope.Unpublished, locale)) { - var pageToDelete = conn.Get() - .Single(p => p.Id == pageId && p.VersionId == versionId); + var pages = conn.Get().Where(p => p.Id == pageId).ToList(); + if (pages.Count == 1 && pages[0].VersionId == versionId) + { + DeletePage(pages[0]); + return; + } + } + + var publicationScopes = new[] {PublicationScope.Published, PublicationScope.Unpublished}; - var placeholders = conn.Get() - .Where(p => p.PageId == pageId && p.VersionId == versionId).ToList(); + using (var transactionScope = TransactionsFacade.CreateNewScope()) + { + foreach (var publicationScope in publicationScopes) + { + using (var conn = new DataConnection(publicationScope, locale)) + { + var pageToDelete = conn.Get() + .SingleOrDefault(p => p.Id == pageId && p.VersionId == versionId); - DataFacade.Delete(placeholders, false, false); - DataFacade.Delete(pageToDelete); + var placeholders = conn.Get() + .Where(p => p.PageId == pageId && p.VersionId == versionId).ToList(); - scope.Complete(); + if (placeholders.Any()) + { + DataFacade.Delete(placeholders, false, false); + } + + if (pageToDelete != null) + { + DataFacade.Delete(pageToDelete); + } + } + } + + transactionScope.Complete(); } } - internal static bool AddPageTypePageFoldersAndApplications(IPage page) { #warning Validate that having a page type with associated PageType PageFolders or Applications does not break on 2nd add for same page id From 4baefc6693888f6bdae7c3c9bf1cf7bc4a15035f Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 12 Aug 2016 17:25:54 +0200 Subject: [PATCH 191/213] Fixed a null reference when browsing page versions --- Composite/Data/Caching/CachingQueryable.cs | 47 ++++++++++------------ 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/Composite/Data/Caching/CachingQueryable.cs b/Composite/Data/Caching/CachingQueryable.cs index 298216c501..56b64655a4 100644 --- a/Composite/Data/Caching/CachingQueryable.cs +++ b/Composite/Data/Caching/CachingQueryable.cs @@ -214,33 +214,34 @@ public object Execute(Expression expression) public IData GetCachedValueByKey(object key) { - IEnumerable cachedRows = GetRowsByKeyTable()[key]; - if (cachedRows == null) return null; - - IEnumerable filteredData = cachedRows.Cast(); - - var filterMethodInfo = StaticReflection.GetGenericMethodInfo( - () => ((DataInterceptor)null).InterceptGetData((IEnumerable)null)) - .MakeGenericMethod(typeof(T)); - - foreach (var dataInterceptor in DataFacade.GetDataInterceptors(typeof(T))) - { - filteredData = (IEnumerable)filterMethodInfo.Invoke(dataInterceptor, new object[] { filteredData }); - } + var filteredData = GetFromCacheFiltered(key); + if (filteredData == null) return null; var result = filteredData.FirstOrDefault(); - if (result == null) - { - return null; - } + if (result == null) return null; return _wrappingMethodInfo.Invoke(null, new object[] { result }) as IData; } + public IEnumerable GetCachedVersionValuesByKey(object key) { - IEnumerable cachedRows = GetRowsByKeyTable()[key]; - if (cachedRows == null) return null; + var result = GetFromCacheFiltered(key); + if (result == null) return Enumerable.Empty(); + + return _listWrappingMethodInfo.Invoke(null, new object[] { result }) as IEnumerable; + } + + + private IEnumerable GetFromCacheFiltered(object key) + { + var cachedTable = GetRowsByKeyTable(); + IEnumerable cachedRows; + + if (!cachedTable.TryGetValue(key, out cachedRows)) + { + return null; + } IEnumerable filteredData = cachedRows.Cast(); @@ -253,13 +254,7 @@ public IEnumerable GetCachedVersionValuesByKey(object key) filteredData = (IEnumerable)filterMethodInfo.Invoke(dataInterceptor, new object[] { filteredData }); } - var result = filteredData; - if (result == null) - { - return null; - } - - return _listWrappingMethodInfo.Invoke(null, new object[] { result }) as IEnumerable; + return filteredData; } From 6ee553028f77b774252439c23160d22b3884406b Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 15 Aug 2016 13:49:59 +0200 Subject: [PATCH 192/213] Making some client validation messages localizable --- .../Composite.Management.en-us.xml | 14 ++++++++++- .../top/ui/bindings/data/DataBinding.js | 24 ++++++++++++++++--- .../data/keyboard/DataInputBinding.js | 4 ++-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Website/Composite/localization/Composite.Management.en-us.xml b/Website/Composite/localization/Composite.Management.en-us.xml index 3ab892ee69..552404f109 100644 --- a/Website/Composite/localization/Composite.Management.en-us.xml +++ b/Website/Composite/localization/Composite.Management.en-us.xml @@ -678,7 +678,19 @@ - + + + + + + + + + + + + + diff --git a/Website/Composite/scripts/source/top/ui/bindings/data/DataBinding.js b/Website/Composite/scripts/source/top/ui/bindings/data/DataBinding.js index 3fe23b3b8b..98726597e2 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/data/DataBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/data/DataBinding.js @@ -25,6 +25,24 @@ EventBroadcaster.subscribe ( BroadcastMessages.APPLICATION_LOGIN, { expressions.each ( function ( entry ) { DataBinding.expressions [ entry.Key ] = new RegExp ( entry.Value ); }); + + var localizedWarnings = { + "required": StringBundle.getString("ui", "Validation.Required"), + "number": StringBundle.getString("ui", "Validation.InvalidField.Number"), + "integer": StringBundle.getString("ui", "Validation.InvalidField.Integer"), + "programmingidentifier": StringBundle.getString("ui", "Validation.InvalidField.ProgrammingIdentifier"), + "programmingnamespace": StringBundle.getString("ui", "Validation.InvalidField.ProgrammingNamespace"), + "url": StringBundle.getString("ui", "Validation.InvalidField.Url"), + "minlength": StringBundle.getString("ui", "Validation.StringLength.Min"), + "maxlength": StringBundle.getString("ui", "Validation.StringLength.Max"), + "currency": StringBundle.getString("ui", "Validation.InvalidField.Currency"), + "email": StringBundle.getString("ui", "Validation.InvalidField.Email"), + "guid": StringBundle.getString("ui", "Validation.InvalidField.Guid") + } + + for (var prop in localizedWarnings) { + DataBinding.warnings[prop] = localizedWarnings[prop]; + } } }); @@ -56,15 +74,15 @@ DataBinding.expressions = { * TODO: Move to ConfigurationService? */ DataBinding.warnings = { - + "required" : "Required", "number" : "Numbers only", "integer" : "Integers only", "programmingidentifier" : "Invalid identifier", "programmingnamespace" : "Invalid namespace", "url" : "Invalid URL", - "minlength" : "${count} characters minimum", - "maxlength" : "${count} characters maximum", + "minlength" : "{0} characters minimum", + "maxlength" : "{0} characters maximum", "currency" : "Invalid notation", "email" : "Invalid e-mail", "guid" : "Invalid GUID" diff --git a/Website/Composite/scripts/source/top/ui/bindings/data/keyboard/DataInputBinding.js b/Website/Composite/scripts/source/top/ui/bindings/data/keyboard/DataInputBinding.js index e9a0093c28..d57d13adbb 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/data/keyboard/DataInputBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/data/keyboard/DataInputBinding.js @@ -723,10 +723,10 @@ DataInputBinding.prototype.validate = function ( isInternal ) { message = DataBinding.warnings [ "required" ]; } else if ( this._isInvalidBecauseMinLength == true ) { message = DataBinding.warnings [ "minlength" ]; - message = message.replace ( "${count}", String ( this.minlength )); + message = message.replace ( "{0}", String ( this.minlength )); } else if ( this._isInvalidBecauseMaxLength == true ) { message = DataBinding.warnings [ "maxlength" ]; - message = message.replace ( "${count}", String ( this.maxlength )); + message = message.replace ( "{0}", String ( this.maxlength )); } else { message = DataBinding.warnings [ this.type ]; } From 71df01bef62fa7d31a133bd95e9cfd6ebcbae61d Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 16 Aug 2016 17:03:34 +0200 Subject: [PATCH 193/213] Fixing a compilation error --- .../WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs b/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs index 8f245de343..8ff273b1bd 100644 --- a/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs +++ b/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs @@ -97,7 +97,7 @@ protected override void OnPreRender(EventArgs e) try { var mappings = new Dictionary(); - FormFlowUiDefinitionRenderer.ResolveBindingPathToCliendIDMappings(GetContainer(), mappings); + FormFlowUiDefinitionRenderer.ResolveBindingPathToClientIDMappings(GetContainer(), mappings); var control = new HtmlGenericControl("ui:resolvercontainer"); control.Attributes.Add("class", "resolvercontainer hide"); foreach (var mapping in mappings) From b2d005f792cba0b2c391960979961f4b4c965741 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Thu, 18 Aug 2016 10:47:45 +0200 Subject: [PATCH 194/213] Show Live version first in browser + refactoring --- Composite/Data/Types/IVersionedDataHelper.cs | 106 ++++++++++++++---- .../PageElementProvider.cs | 4 +- 2 files changed, 88 insertions(+), 22 deletions(-) diff --git a/Composite/Data/Types/IVersionedDataHelper.cs b/Composite/Data/Types/IVersionedDataHelper.cs index 6fd90e14d0..b72a2923a6 100644 --- a/Composite/Data/Types/IVersionedDataHelper.cs +++ b/Composite/Data/Types/IVersionedDataHelper.cs @@ -5,37 +5,60 @@ namespace Composite.Data.Types { /// - /// Represents a data item with timed publishing + /// Contract for defining IVersioned extra methods and helper functions /// - public abstract class VersionedPageHelperContract + public abstract class VersionedDataHelperContract { - /// + /// + /// Returns version name for the IVersioned data if it could otherwise returns null + /// public abstract string LocalizedVersionName(T data) where T : IVersioned; - /// - public abstract DateTime? GetPublishDate(T data) where T : IVersioned; + /// + /// Returns currently live version name for the IVersioned data if it could otherwise returns null + /// + public abstract string GetLiveVersionName(T data) where T : IVersioned; + + /// + /// Returns column name and tooltip for the extra fields in publication overview, if no extra fields needed returns null + /// + public abstract List GetExtraPropertiesNames(); + + /// + /// Returns values for the extra fields in publication overview, if no extra fields needed returns null + /// + public abstract List GetExtraProperties(T data) where T : IVersioned; - /// - public abstract DateTime? GetUnpublishDate(T data) where T : IVersioned; } - /// - public static class VersionedPageHelper + /// + /// This class should be used in every versioning package to register it's naming and detecting of live versions + /// + public static class VersionedDataHelper { - private static List _instances; + private static List _instances; - /// - public static void RegisterVersionHelper(VersionedPageHelperContract vpc) + /// + /// Returns if there are any versioning package instances available + /// + public static bool IsThereAnyVersioningServices => _instances!=null && _instances.Count > 0; + + /// + /// Registers instances of versioning packages + /// + public static void RegisterVersionHelper(VersionedDataHelperContract vpc) { if (_instances == null) { - _instances = new List(); + _instances = new List(); } _instances.Add(vpc); } - /// + /// + /// Returns version name for the IVersioned data + /// public static string LocalizedVersionName(this T str) where T : IVersioned { var defaultVersionName = @@ -50,16 +73,59 @@ public static string LocalizedVersionName(this T str) where T : IVersioned string.Join(",", _instances.Select(p => p.LocalizedVersionName(str)).Where(name => name != null)): defaultVersionName; } - /// - public static DateTime? GetPublishDate(this IVersioned data) + /// + /// Returns column name and tooltip for the extra fields in publication overview + /// + public static List GetExtraPropertiesNames() { - return _instances?.Select(p => p.GetPublishDate(data)).FirstOrDefault(date => date != null); + return _instances?.SelectMany(p => p.GetExtraPropertiesNames()).ToList(); } - /// - public static DateTime? GetUnpublishDate(this IVersioned data) + /// + /// Returns values for the extra fields in publication overview + /// + public static List GetExtraProperties(this T str) where T : IVersioned { - return _instances?.Select(p => p.GetUnpublishDate(data)).FirstOrDefault(date => date != null); + return _instances?.SelectMany(p => p.GetExtraProperties(str)).ToList(); } + + /// + /// Returns currently live version name for the IVersioned data + /// + public static string GetLiveVersionName(this T str) where T : IVersioned + { + if (_instances == null) + { + return null; + } + + return _instances.Select(p => p.GetLiveVersionName(str)).Any(name => name != null) ? + string.Join(",", _instances.Select(p => p.GetLiveVersionName(str)).Where(name => name != null)) : null; + } + + } + + /// + /// Represents Extra fields that a Versioning package needs to insert to the publication overview + /// + public class VersionedExtraProperties + { + /// + public string ColumnName; + /// + public string Value; + /// + public string SortableValue; + } + + /// + /// Represents column title and tooltip for the Extra fields that a Versioning package needs to insert to the publication overview + /// + public class VersionedExtraPropertiesColumnInfo + { + /// + public string ColumnName; + /// + public string ColumnTooltip; } } diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index c291dce342..a2d9d9ae6c 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -462,7 +462,7 @@ private IEnumerable GetChildrenPages(EntityToken entityToken, SearchToken if (!searchToken.IsValidKeyword()) { - return PageServices.GetChildren(itemId.Value).Evaluate().AsQueryable(); + return PageServices.GetChildren(itemId.Value).Evaluate().AsQueryable().OrderByDescending(f => f.LocalizedVersionName() == f.GetLiveVersionName()); } string keyword = searchToken.Keyword.ToLowerInvariant(); @@ -495,7 +495,7 @@ from page in DataFacade.GetData() } } - return pages.AsQueryable(); + return pages.AsQueryable().OrderByDescending(f => f.LocalizedVersionName() == f.GetLiveVersionName()); } From 6927bfcbedc7f8a2142cefefec74346ec91cb7cd Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Thu, 18 Aug 2016 10:49:17 +0200 Subject: [PATCH 195/213] Making Unpublished Overview pages columns dynamic, Versioning packages can inject their columns into it --- .../ViewUnpublishedItems.aspx | 25 +-- .../ViewUnpublishedItems.aspx.cs | 6 + .../ViewUnpublishedItems.css | 5 - .../bindings/UnpublishedPageBinding.js | 30 ++-- .../Composite/services/Tree/TreeServices.asmx | 151 +++++++----------- 5 files changed, 94 insertions(+), 123 deletions(-) diff --git a/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx b/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx index cdf58bb054..803746b02c 100644 --- a/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx +++ b/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx @@ -1,6 +1,7 @@  <%@ Page Language="C#" AutoEventWireup="true" CodeFile="ViewUnpublishedItems.aspx.cs" Inherits="ViewUnpublishedItems" %> +<%@ Import Namespace="Composite.Data.Types" %> @@ -31,16 +32,20 @@ - - - - - - - - - - + <% if (VersionedDataHelper.IsThereAnyVersioningServices) + { %> + + <% } %> + + + + + + + + + + diff --git a/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx.cs b/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx.cs index 8b3e1a11b5..7d25a1b311 100644 --- a/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx.cs +++ b/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.aspx.cs @@ -1,9 +1,15 @@ using System; +using Composite.Data.Types; public partial class ViewUnpublishedItems : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { + if (!this.IsPostBack) + { + headerRepeater.DataSource = VersionedDataHelper.GetExtraPropertiesNames(); + headerRepeater.DataBind(); + } } diff --git a/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.css b/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.css index cba0478c66..8951f8fdd4 100644 --- a/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.css +++ b/Website/Composite/content/views/publishworkflowstatus/ViewUnpublishedItems.css @@ -29,11 +29,6 @@ a { cursor: pointer; } -.table.noversion th.version, -.table.noversion td.version { - display: none; -} - .table ui|clickbutton.sortbutton ui|labelbox { border: inherit; color: inherit; diff --git a/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js b/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js index 3aa79ef93c..cbaebe6c4d 100644 --- a/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js +++ b/Website/Composite/content/views/publishworkflowstatus/bindings/UnpublishedPageBinding.js @@ -181,22 +181,20 @@ UnpublishedPageBinding.prototype.renderTable = function (nodes, selected) { } linkcell.appendChild(link); - this.addTextCell(row, node.getPropertyBag().Version, - { - "class": "version" - }); - this.addTextCell(row, node.getPropertyBag().Status); - this.addTextCell(row, node.getPropertyBag().PublishDate, - { - "data-sort-value": node.getPropertyBag().SortablePublishDate , - } - ); - this.addTextCell(row, node.getPropertyBag().UnpublishDate, { - "data-sort-value": node.getPropertyBag().SortableUnpublishDate, - }); - this.addTextCell(row, node.getPropertyBag().Created).setAttribute("data-sort-value", node.getPropertyBag().SortableCreated); - this.addTextCell(row, node.getPropertyBag().Modified).setAttribute("data-sort-value", node.getPropertyBag().SortableModified); - this.addTextCell(row, ""); + var headers = this.table.firstElementChild.firstElementChild.cells; + var propertybag = node.getPropertyBag(); + + for (var i = 2; i < headers.length; i++) { + var propertyName = headers[i].innerText.trim(); + if (propertyName in propertybag) { + this.addTextCell(row, propertybag[propertyName]). + setAttribute("data-sort-value", ((propertyName + "Sortable") in propertybag) ? + propertybag[propertyName + "Sortable"] : + propertybag[propertyName]); + } else { + this.addTextCell(row, ""); + } + } }, this); diff --git a/Website/Composite/services/Tree/TreeServices.asmx b/Website/Composite/services/Tree/TreeServices.asmx index 6f6737773b..7c9197b62a 100644 --- a/Website/Composite/services/Tree/TreeServices.asmx +++ b/Website/Composite/services/Tree/TreeServices.asmx @@ -14,6 +14,7 @@ using Composite.C1Console.Security; using Composite.C1Console.Users; using Composite.Core; using Composite.Core.IO; +using Composite.Core.ResourceSystem; using Composite.Core.Routing; using Composite.Core.Xml; using Composite.Core.Types; @@ -23,16 +24,14 @@ using Composite.Core.WebClient.Services.TreeServiceObjects.ExtensionMethods; using Composite.Data; using Composite.Data.ProcessControlled; using Composite.Data.ProcessControlled.ProcessControllers.GenericPublishProcessController; -using Composite.Data.PublishScheduling; using Composite.Data.Types; using Composite.Core.Extensions; +using Texts = Composite.Core.ResourceSystem.LocalizationFiles.Composite_Plugins_PageElementProvider; // Search token stuff using Composite.Plugins.Elements.ElementProviders.MediaFileProviderElementProvider; using Composite.Plugins.Elements.ElementProviders.AllFunctionsElementProvider; -using Texts = Composite.Core.ResourceSystem.LocalizationFiles.Composite_Management; - namespace Composite.Services { [WebService(Namespace = "http://www.composite.net/ns/management")] @@ -91,133 +90,101 @@ namespace Composite.Services var publicationStates = new Dictionary { - {GenericPublishProcessController.Draft, Texts.PublishingStatus_draft}, - {GenericPublishProcessController.AwaitingApproval, Texts.PublishingStatus_awaitingApproval}, - {GenericPublishProcessController.AwaitingPublication, Texts.PublishingStatus_awaitingPublication}, + {GenericPublishProcessController.Draft, StringResourceSystemFacade.GetString("Composite.Management", "PublishingStatus.draft")}, + {GenericPublishProcessController.AwaitingApproval, StringResourceSystemFacade.GetString("Composite.Management", "PublishingStatus.awaitingApproval")}, + {GenericPublishProcessController.AwaitingPublication, StringResourceSystemFacade.GetString("Composite.Management", "PublishingStatus.awaitingPublication")} }; List> actionRequiredPages = (from element in allElements - let publishControlledData = (IPublishControlled)((DataEntityToken)element.ElementHandle.EntityToken).Data - where publishControlledData.PublicationStatus != "published" - select new Tuple(element, publishControlledData)).ToList(); + let publishControlledData = (IPublishControlled)((DataEntityToken)element.ElementHandle.EntityToken).Data + where publishControlledData.PublicationStatus != "published" + select new Tuple(element, publishControlledData)).ToList(); foreach (var actionRequiredPage in actionRequiredPages) { var propertyBag = actionRequiredPage.Item1.PropertyBag; var data = actionRequiredPage.Item2; - propertyBag["Title"] = data.GetLabel(); + + propertyBag[Texts.ViewUnpublishedItems_PageTitleLabel] = data.GetLabel(); var publicationStatus = data.PublicationStatus; - propertyBag["Status"] = publicationStates.ContainsKey(publicationStatus) + propertyBag[Texts.ViewUnpublishedItems_StatusLabel] = publicationStates.ContainsKey(publicationStatus) ? publicationStates[publicationStatus] : "Unknown State"; var versionedData = data as IVersioned; - if (versionedData != null) - { - string versionName = versionedData.LocalizedVersionName(); // TODO: type cast? - if (!string.IsNullOrEmpty(versionName)) - { - propertyBag["Version"] = versionName; - } - } + if (versionedData != null) + { + string versionName = versionedData.LocalizedVersionName(); // TODO: type cast? + if (!string.IsNullOrEmpty(versionName)) + { + propertyBag[Texts.ViewUnpublishedItems_VersionLabel] = versionName; + } + } Func toSortableString = date => String.Format("{0:s}", date); var changeHistory = data as IChangeHistory; if (changeHistory != null) { - propertyBag["Modified"] = changeHistory.ChangeDate.ToTimeZoneDateTimeString(); - propertyBag["SortableModified"] = toSortableString(changeHistory.ChangeDate); - propertyBag["ChangedBy"] = changeHistory.ChangedBy; + propertyBag[Texts.ViewUnpublishedItems_DateModifiedLabel] = changeHistory.ChangeDate.ToTimeZoneDateTimeString(); + propertyBag[Texts.ViewUnpublishedItems_DateModifiedLabel+"Sortable"] = toSortableString(changeHistory.ChangeDate); } var creationHistory = data as ICreationHistory; if (creationHistory != null) { - propertyBag["Created"] = + propertyBag[Texts.ViewUnpublishedItems_DateCreatedLabel] = creationHistory.CreationDate.ToTimeZoneDateTimeString(); - propertyBag["SortableCreated"] = + propertyBag[Texts.ViewUnpublishedItems_DateCreatedLabel+"Sortable"] = toSortableString(creationHistory.CreationDate); } - DateTime? publishDate, unpublishDate; - GetPublicationDates(data, out publishDate, out unpublishDate); - - if (publishDate != null) - { - propertyBag["PublishDate"] = publishDate.ToTimeZoneDateTimeString(); - } - propertyBag["SortablePublishDate"] = toSortableString(publishDate ?? DateTime.MinValue); - - - if (unpublishDate != null) - { - propertyBag["UnpublishDate"] = unpublishDate.ToTimeZoneDateTimeString(); - } - propertyBag["SortableUnpublishDate"] = toSortableString(unpublishDate ?? DateTime.MinValue); + if (data is IVersioned) + { + foreach (var x in (data as IVersioned).GetExtraProperties() ?? new List()) + { + propertyBag[x.ColumnName] = x.Value; + propertyBag[x.ColumnName + "Sortable"] = x.SortableValue; + + } + } + } return actionRequiredPages.Select(pair => pair.Item1).ToList().ToClientElementList(); } - private void GetPublicationDates(IData data, out DateTime? publicationDate, out DateTime? unpublicationDate) - { - var versionedData = data as IVersioned; - - if (versionedData != null) - { - publicationDate = versionedData.GetPublishDate(); - unpublicationDate = versionedData.GetUnpublishDate(); - } - else - { - var interfaceType = data.DataSourceId.InterfaceType; - string key = data.GetUniqueKey().ToString(); - string cultureName = UserSettings.ActiveLocaleCultureInfo.Name; - - var publishSchedule = PublishScheduleHelper.GetPublishSchedule( - interfaceType, key, cultureName); + IEnumerable GetPublishControlledDescendants(ElementHandle elementHandle) + { + HashSet elementBundles = null; - publicationDate = publishSchedule != null ? (DateTime?)publishSchedule.PublishDate : null; + var children = ElementFacade.GetChildren(elementHandle, new SearchToken()) ?? Enumerable.Empty(); + foreach (var child in children) + { + if (IsPublishControlled(child)) + { + yield return child; + } - var unpublishSchedule = PublishScheduleHelper.GetUnpublishSchedule( - interfaceType, key, cultureName); + string elementBundle = child.VisualData.ElementBundle; + if (elementBundle != null) + { + elementBundles = elementBundles ?? new HashSet(); + if (elementBundles.Contains(elementBundle)) + { + continue; + } - unpublicationDate = unpublishSchedule != null ? (DateTime?)unpublishSchedule.UnpublishDate : null; - } - } + elementBundles.Add(elementBundle); + } - IEnumerable GetPublishControlledDescendants(ElementHandle elementHandle) - { - HashSet elementBundles = null; - - var children = ElementFacade.GetChildren(elementHandle, new SearchToken()) ?? Enumerable.Empty(); - foreach (var child in children) - { - if (IsPublishControlled(child)) - { - yield return child; - } - - string elementBundle = child.VisualData.ElementBundle; - if (elementBundle != null) - { - elementBundles = elementBundles ?? new HashSet(); - if (elementBundles.Contains(elementBundle)) - { - continue; - } - - elementBundles.Add(elementBundle); - } - - foreach (var element in GetPublishControlledDescendants(child.ElementHandle)) - { - yield return element; - } - } - } + foreach (var element in GetPublishControlledDescendants(child.ElementHandle)) + { + yield return element; + } + } + } private bool IsPublishControlled(Element v) { From 086012808e06b30ab41639ed50f10d0cf81a4cf7 Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Fri, 19 Aug 2016 15:01:29 +0200 Subject: [PATCH 196/213] Fixing issue where editing pages with metadfata fail with an XML/XSLT enception on post render steps. Done by removing from form ui templates - this element was included in the original e2e branch to help resolve input fields in forms, but the attribute generation on the element could generate illegal names (specifically containing :). --- .../TemplatedUiContainerBase.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs b/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs index 3f3d184c0e..d465a83e18 100644 --- a/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs +++ b/Composite/Plugins/Forms/WebChannel/UiContainerFactories/TemplatedUiContainerBase.cs @@ -87,25 +87,6 @@ protected override void OnPreRender(EventArgs e) } } - if (RuntimeInformation.IsTestEnvironment) { - - try - { - var mappings = new Dictionary(); - FormFlowUiDefinitionRenderer.ResolveBindingPathToCliendIDMappings(GetContainer(), mappings); - var control = new HtmlGenericControl("ui:resolvercontainer"); - control.Attributes.Add("class", "resolvercontainer hide"); - foreach (var mapping in mappings) - { - control.Attributes.Add($"data-{mapping.Key}", mapping.Value); - } - GetFormPlaceHolder().Controls.Add(control); - } - catch { - //Nothing - } - } - base.OnPreRender(e); } From 37fc7ba8b8ff86170e94473d70de6104a088b274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gert=20S=C3=B8nderby?= Date: Fri, 19 Aug 2016 15:28:44 +0200 Subject: [PATCH 197/213] Added note to readme about expectations re. port number. --- Website/test/e2e/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Website/test/e2e/README.md b/Website/test/e2e/README.md index de77fef16f..f2329a54db 100644 --- a/Website/test/e2e/README.md +++ b/Website/test/e2e/README.md @@ -14,6 +14,8 @@ The next step is installing nightwatch itself. This is done by issuing the comma Finally, navigate to the root directory of your C1 working copy. This will usually be named `C1-CMS`, but you may have named it otherwise when cloning it. In this directory, you can then start the tests by running `nightwatch` from your command line. This will run the whole test suite, starting with installing the Venus starter site. +Nightwatch expects to find the CMS running on localhost, port 8080. You can either change your Visual Studio or WebMatrix/IIS setup to use this port, or edit the `nightwatch.json` configuration file found in the project root. You can edit the line that says `"launch_url" : "http://localhost:8080"` to reflect the URL used. + Due to certain technical limitations, nightwatch must always be run from the working directory, and cannot be run from any subdirectory. ## Running tests separately From 551eebd5fabd9d6f4c497f43367cc3ea75471292 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 19 Aug 2016 15:46:55 +0200 Subject: [PATCH 198/213] Fixing DataScope related enabling/disabling services --- .../Core/Extensions/IQueryableExtensionMethods.cs | 8 +++++++- Composite/Data/DataScope.cs | 10 +++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Composite/Core/Extensions/IQueryableExtensionMethods.cs b/Composite/Core/Extensions/IQueryableExtensionMethods.cs index 1d772f5009..64a18b7853 100644 --- a/Composite/Core/Extensions/IQueryableExtensionMethods.cs +++ b/Composite/Core/Extensions/IQueryableExtensionMethods.cs @@ -4,6 +4,7 @@ using System.Reflection; using Composite.Core.Types; using Composite.Data; +using Composite.Data.Caching; using Composite.Data.Foundation; namespace Composite.Core.Extensions @@ -46,7 +47,12 @@ public static bool IsEnumerableQuery(this IQueryable query) return (query as DataFacadeQueryable).IsEnumerableQuery; } - return query.GetType().GetGenericTypeDefinition().FullName.StartsWith("System.Linq.EnumerableQuery"); + if (query is CachingQueryable) + { + return true; + } + + return query.GetType().GetGenericTypeDefinition() == typeof(EnumerableQuery<>); } diff --git a/Composite/Data/DataScope.cs b/Composite/Data/DataScope.cs index bbb791c9f0..681d915e8e 100644 --- a/Composite/Data/DataScope.cs +++ b/Composite/Data/DataScope.cs @@ -14,6 +14,7 @@ public sealed class DataScope : IDisposable private readonly bool _cultureInfoPushed; private bool _dataServicePushed; private bool _disposed; + private bool _servicesDisabled; /// public void AddService(object service) @@ -116,7 +117,10 @@ void Dispose(bool disposing) throw new ObjectDisposedException(nameof(DataScope)); } - EnableServices(); + if (_servicesDisabled) + { + EnableServices(); + } if (_dataScopePushed) { @@ -139,11 +143,15 @@ void Dispose(bool disposing) internal void DisableServices() { DataServiceScopeManager.DisableServices(); + + _servicesDisabled = true; } internal void EnableServices() { DataServiceScopeManager.EnableServices(); + + _servicesDisabled = false; } } } From 356a66d2b46d0bc56e8d14d252bf0791f4eafc72 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 19 Aug 2016 16:01:35 +0200 Subject: [PATCH 199/213] Fixing the build --- Website/WebSite.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Website/WebSite.csproj b/Website/WebSite.csproj index 55b237901e..a424a5a963 100644 --- a/Website/WebSite.csproj +++ b/Website/WebSite.csproj @@ -324,10 +324,10 @@ log.aspx - + ViewUnpublishedItems.aspx ASPXCodeBehind - + Default.aspx ASPXCodeBehind From cff2548d209fb10aeee3ac913d494d1f222bf847 Mon Sep 17 00:00:00 2001 From: Morteza Kasravi Date: Fri, 19 Aug 2016 17:59:36 +0200 Subject: [PATCH 200/213] fix unpublished view error when only unversioned unpublished items existed --- Composite/Data/Types/IVersionedDataHelper.cs | 4 ++-- .../Composite/services/Tree/TreeServices.asmx | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Composite/Data/Types/IVersionedDataHelper.cs b/Composite/Data/Types/IVersionedDataHelper.cs index b72a2923a6..ee1087ef00 100644 --- a/Composite/Data/Types/IVersionedDataHelper.cs +++ b/Composite/Data/Types/IVersionedDataHelper.cs @@ -78,7 +78,7 @@ public static string LocalizedVersionName(this T str) where T : IVersioned ///
public static List GetExtraPropertiesNames() { - return _instances?.SelectMany(p => p.GetExtraPropertiesNames()).ToList(); + return _instances?.SelectMany(p => p.GetExtraPropertiesNames() ?? new List()).ToList(); } /// @@ -86,7 +86,7 @@ public static List GetExtraPropertiesNames() /// public static List GetExtraProperties(this T str) where T : IVersioned { - return _instances?.SelectMany(p => p.GetExtraProperties(str)).ToList(); + return _instances?.SelectMany(p => p.GetExtraProperties(str) ?? new List()).ToList(); } /// diff --git a/Website/Composite/services/Tree/TreeServices.asmx b/Website/Composite/services/Tree/TreeServices.asmx index 7c9197b62a..b3334ce854 100644 --- a/Website/Composite/services/Tree/TreeServices.asmx +++ b/Website/Composite/services/Tree/TreeServices.asmx @@ -141,15 +141,25 @@ namespace Composite.Services toSortableString(creationHistory.CreationDate); } - if (data is IVersioned) + try { - foreach (var x in (data as IVersioned).GetExtraProperties() ?? new List()) + if (data is IVersioned) { - propertyBag[x.ColumnName] = x.Value; - propertyBag[x.ColumnName + "Sortable"] = x.SortableValue; + foreach ( + var x in (data as IVersioned).GetExtraProperties() ?? new List()) + { + propertyBag[x.ColumnName] = x.Value; + propertyBag[x.ColumnName + "Sortable"] = x.SortableValue; + } } } + catch (Exception ex) + { + Log.LogCritical(LogTitle, "Problem getting extra properties from version packages"); + Log.LogCritical(LogTitle, ex); + throw; + } } return actionRequiredPages.Select(pair => pair.Item1).ToList().ToClientElementList(); From 8d2d2883ed007aeab00814e225da9093d099f3ba Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 22 Aug 2016 16:12:15 +0200 Subject: [PATCH 201/213] Fixing custom data types initialization --- .../Data/DynamicTypes/DynamicTypeManager.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Composite/Data/DynamicTypes/DynamicTypeManager.cs b/Composite/Data/DynamicTypes/DynamicTypeManager.cs index af6f4a3f86..0d6fb468e1 100644 --- a/Composite/Data/DynamicTypes/DynamicTypeManager.cs +++ b/Composite/Data/DynamicTypes/DynamicTypeManager.cs @@ -47,9 +47,9 @@ public static DataTypeDescriptor GetDataTypeDescriptor(Type typeToDescript) { DataTypeDescriptor dataTypeDescriptor; - if (TryGetDataTypeDescriptor(typeToDescript.GetImmutableTypeId(), out dataTypeDescriptor) == false) + if (!TryGetDataTypeDescriptor(typeToDescript.GetImmutableTypeId(), out dataTypeDescriptor)) { - dataTypeDescriptor = BuildNewDataTypeDescriptor(typeToDescript); ; + dataTypeDescriptor = BuildNewDataTypeDescriptor(typeToDescript); } return dataTypeDescriptor; @@ -59,10 +59,10 @@ public static DataTypeDescriptor GetDataTypeDescriptor(Type typeToDescript) // Overload /// - public static DataTypeDescriptor GetDataTypeDescriptor(Guid immuteableTypeId) + public static DataTypeDescriptor GetDataTypeDescriptor(Guid immutableTypeId) { DataTypeDescriptor dataTypeDescriptor; - TryGetDataTypeDescriptor(immuteableTypeId, out dataTypeDescriptor); + TryGetDataTypeDescriptor(immutableTypeId, out dataTypeDescriptor); return dataTypeDescriptor; } @@ -79,9 +79,9 @@ public static bool TryGetDataTypeDescriptor(Type interfaceType, out DataTypeDesc /// - public static bool TryGetDataTypeDescriptor(Guid immuteableTypeId, out DataTypeDescriptor dataTypeDescriptor) + public static bool TryGetDataTypeDescriptor(Guid immutableTypeId, out DataTypeDescriptor dataTypeDescriptor) { - return _dynamicTypeManager.TryGetDataTypeDescriptor(immuteableTypeId, out dataTypeDescriptor); + return _dynamicTypeManager.TryGetDataTypeDescriptor(immutableTypeId, out dataTypeDescriptor); } @@ -259,17 +259,11 @@ public static void EnsureCreateStore(Type interfaceType) // Helper public static void EnsureCreateStore(Type interfaceType, string providerName) { - DataTypeDescriptor dataTypeDescriptor; - if (!TryGetDataTypeDescriptor(interfaceType, out dataTypeDescriptor)) - { - dataTypeDescriptor = BuildNewDataTypeDescriptor(interfaceType); - } - IEnumerable dynamicProviderNames; if (providerName == null) { - // Checking if any of exising dynamic data providers already has a store for the specified interface type + // Checking if any of existing dynamic data providers already has a store for the specified interface type providerName = DataProviderRegistry.DefaultDynamicTypeDataProviderName; dynamicProviderNames = DataProviderRegistry.DynamicDataProviderNames; } @@ -286,6 +280,8 @@ public static void EnsureCreateStore(Type interfaceType, string providerName) return; } + var dataTypeDescriptor = BuildNewDataTypeDescriptor(interfaceType); + CreateStore(providerName, dataTypeDescriptor, true); CodeGenerationManager.GenerateCompositeGeneratedAssembly(true); } From 990a36073d7ad67c203dbdb2cc558569939a7db4 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 22 Aug 2016 16:29:57 +0200 Subject: [PATCH 202/213] Generating a random VersionId when adding a new page --- .../WebClient/ApplicationLevelEventHandlers.cs | 3 +++ Composite/Data/Types/IVersionedDataHelper.cs | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs index 3d863ec4ae..04d1027266 100644 --- a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs +++ b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs @@ -15,6 +15,7 @@ using Composite.Core.Routing; using Composite.Core.Threading; using Composite.Core.Types; +using Composite.Data.Types; using Composite.Functions; using Composite.Plugins.Elements.UrlToEntityToken; using Composite.Plugins.Routing.InternalUrlConverters; @@ -100,6 +101,8 @@ private static void InitializeServices() InternalUrls.Register(new MediaInternalUrlConverter()); InternalUrls.Register(new PageInternalUrlConverter()); + + VersionedDataHelper.Initialize(); } diff --git a/Composite/Data/Types/IVersionedDataHelper.cs b/Composite/Data/Types/IVersionedDataHelper.cs index ee1087ef00..282a1cc661 100644 --- a/Composite/Data/Types/IVersionedDataHelper.cs +++ b/Composite/Data/Types/IVersionedDataHelper.cs @@ -38,6 +38,23 @@ public static class VersionedDataHelper { private static List _instances; + static VersionedDataHelper() + { + DataEvents.OnBeforeAdd += (sender, args) => + { + var page = (IPage) args.Data; + if (page.VersionId == Guid.Empty) + { + page.VersionId = Guid.NewGuid(); + } + }; + } + + internal static void Initialize() + { + // The initialization code is in the static constructor + } + /// /// Returns if there are any versioning package instances available /// From 47863eb1833249a1223fc1cd2d696b1024a7580a Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 22 Aug 2016 17:09:55 +0200 Subject: [PATCH 203/213] Performance optimization - greatly speeding up reads from XmlDataProvider --- .../CodeGeneration/DataWrapperGenerator.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Composite/Data/Foundation/CodeGeneration/DataWrapperGenerator.cs b/Composite/Data/Foundation/CodeGeneration/DataWrapperGenerator.cs index 290b69c1bb..a152626dc9 100644 --- a/Composite/Data/Foundation/CodeGeneration/DataWrapperGenerator.cs +++ b/Composite/Data/Foundation/CodeGeneration/DataWrapperGenerator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Composite.Core.Types; @@ -9,32 +10,38 @@ namespace Composite.Data.Foundation.CodeGeneration { /// /// This class handles data wrapper types and cashing. - /// It will through genereated + /// It will through generated /// data wrapper class types if needed. /// internal static class DataWrapperTypeManager { - private static readonly object _lock = new object(); + private static readonly object _compilationLock = new object(); + + private static readonly ConcurrentDictionary _dataWrappersCache + = new ConcurrentDictionary(); public static Type GetDataWrapperType(Type interfaceType) { - Type wrapperType = TryGetWrapperType(interfaceType.FullName); - if (wrapperType != null) return wrapperType; - - lock (_lock) + return _dataWrappersCache.GetOrAdd(interfaceType, type => { - wrapperType = TryGetWrapperType(interfaceType.FullName); + Type wrapperType = TryGetWrapperType(type.FullName); if (wrapperType != null) return wrapperType; - var codeGenerationBuilder = new CodeGenerationBuilder("DataWrapper:" + interfaceType.FullName); + lock (_compilationLock) + { + wrapperType = TryGetWrapperType(type.FullName); + if (wrapperType != null) return wrapperType; - DataWrapperCodeGenerator.AddDataWrapperClassCode(codeGenerationBuilder, interfaceType); + var codeGenerationBuilder = new CodeGenerationBuilder("DataWrapper:" + type.FullName); - IEnumerable types = CodeGenerationManager.CompileRuntimeTempTypes(codeGenerationBuilder); + DataWrapperCodeGenerator.AddDataWrapperClassCode(codeGenerationBuilder, type); - return types.Single(); - } + IEnumerable types = CodeGenerationManager.CompileRuntimeTempTypes(codeGenerationBuilder); + + return types.Single(); + } + }); } @@ -44,7 +51,7 @@ public static Type GetDataWrapperType(DataTypeDescriptor dataTypeDescriptor) Type wrapperType = TryGetWrapperType(dataTypeDescriptor.GetFullInterfaceName()); if (wrapperType != null) return wrapperType; - lock (_lock) + lock (_compilationLock) { wrapperType = TryGetWrapperType(dataTypeDescriptor.GetFullInterfaceName()); if (wrapperType != null) return wrapperType; From d7bd34d5badf8aa0589af279468d9b1cbfa3aff7 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 23 Aug 2016 10:03:30 +0200 Subject: [PATCH 204/213] Data layer performance optimization --- Composite/Data/Caching/CachingEnumerator.cs | 34 ++++--------------- .../CodeGeneration/DataWrapperGenerator.cs | 26 ++++++++++++++ 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/Composite/Data/Caching/CachingEnumerator.cs b/Composite/Data/Caching/CachingEnumerator.cs index 3f8a633825..eb7ae76991 100644 --- a/Composite/Data/Caching/CachingEnumerator.cs +++ b/Composite/Data/Caching/CachingEnumerator.cs @@ -8,8 +8,8 @@ namespace Composite.Data.Caching { internal sealed class CachingEnumerator : IEnumerator { - private IEnumerator _enumerator; - private Type _wrapperType; + private readonly IEnumerator _enumerator; + private Func _wrapperConstructor; public CachingEnumerator(IEnumerator enumerator) @@ -18,15 +18,7 @@ public CachingEnumerator(IEnumerator enumerator) } - public T Current - { - get - { - object wrapper = Activator.CreateInstance(this.WrapperType, new object[] { _enumerator.Current }); - - return (T)wrapper; - } - } + public T Current => WrapperConstructor(_enumerator.Current); public void Dispose() @@ -35,15 +27,7 @@ public void Dispose() } - object IEnumerator.Current - { - get - { - object wrapper = Activator.CreateInstance(this.WrapperType, new object[] { _enumerator.Current }); - - return wrapper; - } - } + object IEnumerator.Current => WrapperConstructor(_enumerator.Current); public bool MoveNext() @@ -58,16 +42,12 @@ public void Reset() } - private Type WrapperType + private Func WrapperConstructor { get { - if (_wrapperType == null) - { - _wrapperType = DataWrapperTypeManager.GetDataWrapperType(typeof(T)); - } - - return _wrapperType; + return _wrapperConstructor + ?? (_wrapperConstructor = DataWrapperTypeManager.GetWrapperConstructor()); } } } diff --git a/Composite/Data/Foundation/CodeGeneration/DataWrapperGenerator.cs b/Composite/Data/Foundation/CodeGeneration/DataWrapperGenerator.cs index a152626dc9..d8b123124d 100644 --- a/Composite/Data/Foundation/CodeGeneration/DataWrapperGenerator.cs +++ b/Composite/Data/Foundation/CodeGeneration/DataWrapperGenerator.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using Composite.Core.Types; using Composite.Data.DynamicTypes; @@ -20,6 +21,31 @@ internal static class DataWrapperTypeManager private static readonly ConcurrentDictionary _dataWrappersCache = new ConcurrentDictionary(); + delegate T ObjectActivator(T param); + + private static readonly ConcurrentDictionary _dataWrappersActivatorCache + = new ConcurrentDictionary(); + + public static Func GetWrapperConstructor() + { + return (Func < T, T >)_dataWrappersActivatorCache.GetOrAdd(typeof (T), type => + { + var wrapperType = GetDataWrapperType(typeof (T)); + + var param = Expression.Parameter(typeof (T)); + + var constructor = wrapperType.GetConstructors().Single(); + var ctrExpression = Expression.New(constructor, param); + + var lambda = Expression.Lambda(typeof (ObjectActivator), ctrExpression, param); + var activator = (ObjectActivator) lambda.Compile(); + + Func func = obj => activator(obj); + + return func; + }); + } + public static Type GetDataWrapperType(Type interfaceType) { From 19eb55c8135ec63640c550aa4999dd548694fb1d Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 23 Aug 2016 12:19:07 +0200 Subject: [PATCH 205/213] Page tree performance fix --- Composite/Data/Types/IVersionedDataHelper.cs | 4 ++-- .../PageElementProvider/PageElementProvider.cs | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Composite/Data/Types/IVersionedDataHelper.cs b/Composite/Data/Types/IVersionedDataHelper.cs index 282a1cc661..6cb42f691d 100644 --- a/Composite/Data/Types/IVersionedDataHelper.cs +++ b/Composite/Data/Types/IVersionedDataHelper.cs @@ -116,8 +116,8 @@ public static string GetLiveVersionName(this T str) where T : IVersioned return null; } - return _instances.Select(p => p.GetLiveVersionName(str)).Any(name => name != null) ? - string.Join(",", _instances.Select(p => p.GetLiveVersionName(str)).Where(name => name != null)) : null; + var versionNames = _instances.Select(p => p.GetLiveVersionName(str)).Where(name => name != null).ToList(); + return versionNames.Any() ? string.Join(",", versionNames) : null; } } diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index a2d9d9ae6c..0d43b4bc1c 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -462,7 +462,7 @@ private IEnumerable GetChildrenPages(EntityToken entityToken, SearchToken if (!searchToken.IsValidKeyword()) { - return PageServices.GetChildren(itemId.Value).Evaluate().AsQueryable().OrderByDescending(f => f.LocalizedVersionName() == f.GetLiveVersionName()); + return OrderByVersions(PageServices.GetChildren(itemId.Value).Evaluate()); } string keyword = searchToken.Keyword.ToLowerInvariant(); @@ -495,7 +495,18 @@ from page in DataFacade.GetData() } } - return pages.AsQueryable().OrderByDescending(f => f.LocalizedVersionName() == f.GetLiveVersionName()); + return OrderByVersions(pages); + } + + private IEnumerable OrderByVersions(IEnumerable pages) + { + return (from page in pages + group page by page.Id + into pageVersionGroups + let versions = pageVersionGroups.ToList() + let liveVersionName = versions.Count == 1 ? null : versions[0].GetLiveVersionName() + select pageVersionGroups.OrderByDescending(v => v.LocalizedVersionName() == liveVersionName).ToList()) + .SelectMany(v => v); } From c932797648f10e688e4a297d4ce9488fafa6d56a Mon Sep 17 00:00:00 2001 From: Marcus Wendt Date: Tue, 23 Aug 2016 12:33:42 +0200 Subject: [PATCH 206/213] Ensuring that /test folder is removed as part of build/package process. We may need to refine this later on or have the test folder live outside the website folder. --- Website/ReleaseCleanupConfiguration.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/Website/ReleaseCleanupConfiguration.xml b/Website/ReleaseCleanupConfiguration.xml index 07f8b1449d..a3b3412aed 100644 --- a/Website/ReleaseCleanupConfiguration.xml +++ b/Website/ReleaseCleanupConfiguration.xml @@ -20,6 +20,7 @@ + From 78caf118e5c17d41b804703c8b60e9cac30d2444 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 23 Aug 2016 14:59:59 +0200 Subject: [PATCH 207/213] DataPackageFragmentInstaller to assign VersionId to pages and related data --- .../DataPackageFragmentInstaller.cs | 67 +++++++++++++++++-- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs index 1f3438f909..27cb5566ad 100644 --- a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs +++ b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs @@ -33,6 +33,8 @@ public sealed class DataPackageFragmentInstaller : BasePackageFragmentInstaller private Dictionary _dataKeysToBeInstalled; private Dictionary>> _missingDataReferences; + private static Dictionary _pageVersionIds = new Dictionary(); + /// public override IEnumerable Validate() { @@ -154,7 +156,7 @@ private static Type GetInstalledVersionOfPendingType(Type interfaceType, IData d } - private static XElement AddData(DataType dataType, CultureInfo cultureInfo) + private XElement AddData(DataType dataType, CultureInfo cultureInfo) { XElement datasElement = new XElement("Datas"); @@ -165,11 +167,13 @@ private static XElement AddData(DataType dataType, CultureInfo cultureInfo) foreach (XElement addElement in dataType.Dataset) { - IData data = DataFacade.BuildNew(dataType.InterfaceType); + var interfaceType = dataType.InterfaceType; + + IData data = DataFacade.BuildNew(interfaceType); if (!dataType.InterfaceType.IsInstanceOfType(data)) { - dataType.InterfaceType = GetInstalledVersionOfPendingType(dataType.InterfaceType, data); + dataType.InterfaceType = GetInstalledVersionOfPendingType(interfaceType, data); } @@ -177,7 +181,7 @@ private static XElement AddData(DataType dataType, CultureInfo cultureInfo) if (dataType.AllowOverwrite || dataType.OnlyUpdate) { - IData existingData = DataFacade.TryGetDataByUniqueKey(dataType.InterfaceType, dataKey); + IData existingData = DataFacade.TryGetDataByUniqueKey(interfaceType, dataKey); if (data != null) { @@ -196,7 +200,12 @@ private static XElement AddData(DataType dataType, CultureInfo cultureInfo) ILocalizedControlled localizedControlled = data as ILocalizedControlled; if (localizedControlled != null) { - localizedControlled.SourceCultureName = LocalizationScopeManager.MapByType(dataType.InterfaceType).Name; + localizedControlled.SourceCultureName = LocalizationScopeManager.MapByType(interfaceType).Name; + } + + if (data is IVersioned) + { + UpdateVersionId((IVersioned)data); } DataFacade.AddNew(data, false, true, false); // Ignore validation, this should have been done in the validation face @@ -221,6 +230,54 @@ private static XElement AddData(DataType dataType, CultureInfo cultureInfo) return datasElement; } + + private void UpdateVersionId(IVersioned data) + { + if (data.VersionId != Guid.Empty) + { + return; + } + + if (data is IPage) + { + var page = (IPage)data; + + Guid versionId; + + if (_pageVersionIds.TryGetValue(page.Id, out versionId)) + { + page.VersionId = versionId; + } + else + { + page.VersionId = Guid.NewGuid(); + _pageVersionIds[page.Id] = page.VersionId; + } + } + + else if (data is IPagePlaceholderContent) + { + Guid pageId = ((IPagePlaceholderContent)data).PageId; + Guid versionId; + + if (_pageVersionIds.TryGetValue(pageId, out versionId)) + { + data.VersionId = versionId; + } + } + else if (data is IPageData) + { + Guid pageId = ((IPageData)data).PageId; + Guid versionId; + + if (_pageVersionIds.TryGetValue(pageId, out versionId)) + { + data.VersionId = versionId; + } + } + } + + private static DataKeyPropertyCollection CopyFieldValues(DataType dataType, IData data, XElement addElement) { var dataKeyPropertyCollection = new DataKeyPropertyCollection(); From 7da634104a0b6694857f5d69a0cdbd9bdd401377 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Tue, 23 Aug 2016 15:19:40 +0200 Subject: [PATCH 208/213] Fixing an exception in sitemap provider when there are no published pages in one of the locales --- Composite/Data/PageManager.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Composite/Data/PageManager.cs b/Composite/Data/PageManager.cs index 867cf8e50e..b3dcec1d3f 100644 --- a/Composite/Data/PageManager.cs +++ b/Composite/Data/PageManager.cs @@ -232,12 +232,15 @@ internal static void PreloadPageCaching() conn.DisableServices(); var pages = DataFacade.GetData().GroupBy(p => p.Id).Evaluate(); - var dataSourceId = pages.First().First().DataSourceId; - - foreach (var pageVersionsGroup in pages) + if (pages.Count > 0) { - string pageKey = GetCacheKey(pageVersionsGroup.Key, dataSourceId); - _pageCache.Add(pageKey, new ReadOnlyCollection(pageVersionsGroup.ToList())); + var dataSourceId = pages.First().First().DataSourceId; + + foreach (var pageVersionsGroup in pages) + { + string pageKey = GetCacheKey(pageVersionsGroup.Key, dataSourceId); + _pageCache.Add(pageKey, new ReadOnlyCollection(pageVersionsGroup.ToList())); + } } } From 0116772203ec27d9a952517a2757dca9e3aae5f0 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 24 Aug 2016 17:37:05 +0200 Subject: [PATCH 209/213] Data providers - setting VersionId of existing data to be equal to PageId after upgrade --- .../SqlDataProviderStoreManipulator.cs | 36 +++++++++- .../XmlDataProviderStoreManipulator.cs | 68 ++++++++++++++----- 2 files changed, 86 insertions(+), 18 deletions(-) diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/SqlDataProviderStoreManipulator.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/SqlDataProviderStoreManipulator.cs index 78865d58bb..bfa26150d9 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/SqlDataProviderStoreManipulator.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/Foundation/SqlDataProviderStoreManipulator.cs @@ -13,6 +13,7 @@ using Composite.Data; using Composite.Data.DynamicTypes; using Composite.Data.ProcessControlled.ProcessControllers.GenericPublishProcessController; +using Composite.Data.Types; using Composite.Plugins.Data.DataProviders.MSSqlServerDataProvider.Sql; @@ -454,7 +455,7 @@ private void AlterStore(UpdateDataTypeDescriptor updateDataTypeDescriptor, DataT }; } - AppendFields(alteredTableName, changeDescriptor.AddedFields, defaultValues); + AppendFields(alteredTableName, changeDescriptor, changeDescriptor.AddedFields, defaultValues); // Clustered index has to be created first. var createIndexActions = new List>(); @@ -596,7 +597,10 @@ private void DropFields(string tableName, IEnumerable field - private void AppendFields(string tableName, IEnumerable addedFieldDescriptions, Dictionary defaultValues = null) + private void AppendFields(string tableName, + DataTypeChangeDescriptor changeDescriptor, + IEnumerable addedFieldDescriptions, + Dictionary defaultValues = null) { foreach (var addedFieldDescriptor in addedFieldDescriptions) { @@ -607,6 +611,34 @@ private void AppendFields(string tableName, IEnumerable add } CreateColumn(tableName, addedFieldDescriptor, defaultValue); + + // Updating VersionId field + if (addedFieldDescriptor.Name == nameof(IVersioned.VersionId) + && changeDescriptor.AlteredType.SuperInterfaces.Contains(typeof (IVersioned))) + { + string sourceField; + + if (changeDescriptor.AlteredType.DataTypeId == typeof (IPage).GetImmutableTypeId()) + { + sourceField = nameof(IPage.Id); + } + else + { + sourceField = changeDescriptor.AlteredType.Fields + .Where(f => f.InstanceType == typeof(Guid) + && (f.ForeignKeyReferenceTypeName?.Contains(typeof (IPage).FullName) ?? false)) + .OrderByDescending(f => f.Name == nameof(IPageData.PageId)) + .Select(f => f.Name) + .FirstOrDefault(); + } + + if (sourceField != null) + { + string updateVersionIdCommandText = + $"UPDATE [{tableName}] SET [{nameof(IVersioned.VersionId)}] = [{sourceField}]"; + ExecuteNonQuery(updateVersionIdCommandText); + } + } } } diff --git a/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderStoreManipulator.cs b/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderStoreManipulator.cs index c4a2771aa1..185f07a33f 100644 --- a/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderStoreManipulator.cs +++ b/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderStoreManipulator.cs @@ -11,6 +11,7 @@ using Composite.Data.DynamicTypes; using Composite.Data.Plugins.DataProvider; using Composite.Data.ProcessControlled.ProcessControllers.GenericPublishProcessController; +using Composite.Data.Types; namespace Composite.Plugins.Data.DataProviders.XmlDataProvider.Foundation @@ -62,8 +63,10 @@ public static void AlterStore(UpdateDataTypeDescriptor updateDescriptor, XmlProv var oldDataScopeConfigurationElement = fileForLanguage.Value; var newDataScopeConfigurationElement = newConfigurationElement.DataScopes[scopeIdentifier.Name][cultureName]; - newFieldValues = new Dictionary(); - newFieldValues.Add("PublicationStatus", GenericPublishProcessController.Published); + newFieldValues = new Dictionary + { + {"PublicationStatus", GenericPublishProcessController.Published} + }; CopyData(updateDescriptor.ProviderName, dataTypeChangeDescriptor, oldDataScopeConfigurationElement, newDataScopeConfigurationElement, newFieldValues); } @@ -77,8 +80,10 @@ public static void AlterStore(UpdateDataTypeDescriptor updateDescriptor, XmlProv var oldDataScopeConfigurationElement = fileByLanguage.Value; var newDataScopeConfigurationElement = newConfigurationElement.DataScopes[DataScopeIdentifier.AdministratedName][fileByLanguage.Key]; - newFieldValues = new Dictionary(); - newFieldValues.Add("PublicationStatus", GenericPublishProcessController.Published); + newFieldValues = new Dictionary + { + {"PublicationStatus", GenericPublishProcessController.Published} + }; CopyData(updateDescriptor.ProviderName, dataTypeChangeDescriptor, oldDataScopeConfigurationElement, newDataScopeConfigurationElement, newFieldValues, false); } @@ -122,8 +127,10 @@ public static void AlterStore(UpdateDataTypeDescriptor updateDescriptor, XmlProv { var newDataScopeConfigurationElement = newConfigurationElement.DataScopes[dataScopeIdentifier][locale.Name]; - var nfv = new Dictionary(newFieldValues); - nfv.Add("SourceCultureName", locale.Name); + var nfv = new Dictionary(newFieldValues) + { + {"SourceCultureName", locale.Name} + }; CopyData(updateDescriptor.ProviderName, dataTypeChangeDescriptor, oldDataScopeConfigurationElement, newDataScopeConfigurationElement, nfv, false); } @@ -178,6 +185,28 @@ private static void CopyData(string providerName, DataTypeChangeDescriptor dataT List newElements = new List(); + bool addingVersionId = dataTypeChangeDescriptor.AddedFields.Any(f => f.Name == nameof(IVersioned.VersionId)) + && dataTypeChangeDescriptor.AlteredType.SuperInterfaces.Any(s => s == typeof (IVersioned)); + + string versionIdSourceFieldName = null; + if (addingVersionId) + { + if (dataTypeChangeDescriptor.AlteredType.DataTypeId == typeof(IPage).GetImmutableTypeId()) + { + versionIdSourceFieldName = nameof(IPage.Id); + } + else + { + versionIdSourceFieldName = dataTypeChangeDescriptor.AlteredType.Fields + .Where(f => f.InstanceType == typeof(Guid) + && (f.ForeignKeyReferenceTypeName?.Contains(typeof(IPage).FullName) ?? false)) + .OrderByDescending(f => f.Name == nameof(IPageData.PageId)) + .Select(f => f.Name) + .FirstOrDefault(); + } + } + + foreach (XElement oldElement in oldDocument.Root.Elements()) { List newChildAttributes = new List(); @@ -222,25 +251,32 @@ private static void CopyData(string providerName, DataTypeChangeDescriptor dataT foreach (DataFieldDescriptor fieldDescriptor in dataTypeChangeDescriptor.AddedFields) { - if (fieldDescriptor.IsNullable == false - && !newChildAttributes.Any(attr => attr.Name == fieldDescriptor.Name)) + if (addingVersionId && fieldDescriptor.Name == nameof(IVersioned.VersionId) && versionIdSourceFieldName != null) { - XAttribute newChildAttribute = new XAttribute(fieldDescriptor.Name, GetDefaultValue(fieldDescriptor)); + var existingField = (Guid)oldElement.Attribute(versionIdSourceFieldName); + if (existingField != null) + { + newChildAttributes.Add(new XAttribute(fieldDescriptor.Name, existingField)); + continue; + } + } - if (newFieldValues.ContainsKey(fieldDescriptor.Name)) + if (!fieldDescriptor.IsNullable + && !newChildAttributes.Any(attr => attr.Name == fieldDescriptor.Name)) + { + object value; + if (!newFieldValues.TryGetValue(fieldDescriptor.Name, out value)) { - newChildAttribute.SetValue(newFieldValues[fieldDescriptor.Name]); + value = GetDefaultValue(fieldDescriptor); } - newChildAttributes.Add(newChildAttribute); + newChildAttributes.Add(new XAttribute(fieldDescriptor.Name, value)); } else if (newFieldValues.ContainsKey(fieldDescriptor.Name)) { XAttribute attribute = newChildAttributes.SingleOrDefault(attr => attr.Name == fieldDescriptor.Name); - if (attribute != null) - { - attribute.SetValue(newFieldValues[fieldDescriptor.Name]); - } + + attribute?.SetValue(newFieldValues[fieldDescriptor.Name]); } } From 9b26c5d88993649434c7f04a321fd0b559666a70 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 26 Aug 2016 11:11:26 +0200 Subject: [PATCH 210/213] Fixing exception handling on a missing data type --- .../MSSqlServerDataProvider/SqlDataProvider_Stores.cs | 9 +++++++-- .../XmlDataProvider/XmlDataProvider_Stores.cs | 5 ++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/SqlDataProvider_Stores.cs b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/SqlDataProvider_Stores.cs index 3798bb19cb..36019dd189 100644 --- a/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/SqlDataProvider_Stores.cs +++ b/Composite/Plugins/Data/DataProviders/MSSqlServerDataProvider/SqlDataProvider_Stores.cs @@ -508,11 +508,16 @@ private InterfaceGeneratedClassesInfo InitializeStoreTypes(InterfaceConfiguratio try { - interfaceType = dataTypes != null ? dataTypes[dataTypeId] : DataTypeTypesManager.GetDataType(dataTypeDescriptor); + if (dataTypes == null + || !dataTypes.TryGetValue(dataTypeId, out interfaceType) + || interfaceType == null) + { + interfaceType = DataTypeTypesManager.GetDataType(dataTypeDescriptor); + } if (interfaceType == null) { - Log.LogError(LogTitle, "The data interface type '{0}' does not exists and is not code generated. It will not be unusable", dataTypeDescriptor.TypeManagerTypeName); + Log.LogWarning(LogTitle, "The data interface type '{0}' does not exists and is not code generated. It will not be unusable", dataTypeDescriptor.TypeManagerTypeName); return result; } diff --git a/Composite/Plugins/Data/DataProviders/XmlDataProvider/XmlDataProvider_Stores.cs b/Composite/Plugins/Data/DataProviders/XmlDataProvider/XmlDataProvider_Stores.cs index 76a88d7eef..f75d049159 100644 --- a/Composite/Plugins/Data/DataProviders/XmlDataProvider/XmlDataProvider_Stores.cs +++ b/Composite/Plugins/Data/DataProviders/XmlDataProvider/XmlDataProvider_Stores.cs @@ -146,10 +146,9 @@ private void InitializeExistingStores() try { - interfaceType = dataTypes[dataTypeDescriptor.DataTypeId]; - if (interfaceType == null) + if (!dataTypes.TryGetValue(dataTypeDescriptor.DataTypeId, out interfaceType) || interfaceType == null) { - Log.LogError(LogTitle, "The data interface type '{0}' does not exists and is not code generated. It will not be usable", dataTypeDescriptor.TypeManagerTypeName); + Log.LogWarning(LogTitle, "The data interface type '{0}' does not exists and is not code generated. It will not be usable", dataTypeDescriptor.TypeManagerTypeName); continue; } From ccf151dae4c6355d6854c1593acb2b589eaa1f88 Mon Sep 17 00:00:00 2001 From: Taras Nakonechnyi Date: Fri, 26 Aug 2016 15:02:31 +0300 Subject: [PATCH 211/213] Fix restoring focus on updating multiple tree node --- .../scripts/source/top/system/SystemNode.js | 16 ++++++++++++++++ .../top/ui/bindings/system/SystemTreeBinding.js | 16 ++++++++++++++-- .../ui/bindings/system/SystemTreeNodeBinding.js | 6 ++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Website/Composite/scripts/source/top/system/SystemNode.js b/Website/Composite/scripts/source/top/system/SystemNode.js index d031c1b76c..8e85d5d899 100644 --- a/Website/Composite/scripts/source/top/system/SystemNode.js +++ b/Website/Composite/scripts/source/top/system/SystemNode.js @@ -189,6 +189,22 @@ SystemNode.prototype.getHandle = function () { return this._data.ElementKey; } +/** + * @return {List} + */ +SystemNode.prototype.getHandles = function () { + + var result = new List(); + if (this._datas != null) { + this._datas.each(function (data) { + result.add(data.ElementKey); + }); + } else { + result.add(this._data.ElementKey); + } + return result; +} + /** * Not all nodes may be tagged! * @return {string} diff --git a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js index 0cd764360b..9d2e41ea3b 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeBinding.js @@ -355,7 +355,17 @@ SystemTreeBinding.prototype.getTokens = function (treenode) { */ SystemTreeBinding.prototype.registerTreeNodeBinding = function (treenode) { - SystemTreeBinding.superclass.registerTreeNodeBinding.call(this, treenode); + treenode.getHandles().each(function(handle) { + if (this._treeNodeBindings.has(handle)) { + throw "Duplicate treenodehandles registered: " + treenode.getLabel(); + } else { + this._treeNodeBindings.set(handle, treenode); + var map = this._openTreeNodesBackupMap; + if (map != null && map.has(handle)) { + treenode.open(); + } + } + }, this); /* * Update entityToken registry so that we may quickly @@ -423,7 +433,9 @@ SystemTreeBinding.prototype.registerTreeNodeBinding = function (treenode) { */ SystemTreeBinding.prototype.unRegisterTreeNodeBinding = function (treenode) { - SystemTreeBinding.superclass.unRegisterTreeNodeBinding.call(this, treenode); + treenode.getHandles().each(function(handle) { + this._treeNodeBindings.del(handle); + }, this); /* * Unregister from entityToken registry. diff --git a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js index b62520aa22..89fd7959b3 100644 --- a/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js +++ b/Website/Composite/scripts/source/top/ui/bindings/system/SystemTreeNodeBinding.js @@ -615,6 +615,11 @@ SystemTreeNodeBinding.prototype.hasChildren = function () { return this.node.hasChildren (); }; +SystemTreeNodeBinding.prototype.getHandles = function () { + + return this.node.getHandles(); +} + /** * @param {string} entityToken */ @@ -625,6 +630,7 @@ SystemTreeNodeBinding.prototype.selectToken = function (entityToken) { this.setLabel(this.node.getLabel()); this.setToolTip(this.node.getToolTip()); this.setImage(this.computeImage()); + this.setHandle(this.node.getHandle()); } /** From a56a31cd052b9289739467e99c7bb16464fe8091 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 26 Aug 2016 14:07:33 +0200 Subject: [PATCH 212/213] Localizing "More" button that's shown when there are too many actions to show --- .../Composite/content/dialogs/treeselector/treeselector.aspx | 2 +- Website/Composite/content/views/browser/browser.aspx | 2 +- Website/Composite/localization/Composite.Management.en-us.xml | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Website/Composite/content/dialogs/treeselector/treeselector.aspx b/Website/Composite/content/dialogs/treeselector/treeselector.aspx index bd8de8e680..9c96b89a32 100644 --- a/Website/Composite/content/dialogs/treeselector/treeselector.aspx +++ b/Website/Composite/content/dialogs/treeselector/treeselector.aspx @@ -31,7 +31,7 @@ - + diff --git a/Website/Composite/content/views/browser/browser.aspx b/Website/Composite/content/views/browser/browser.aspx index 314038126b..b497caf0a9 100644 --- a/Website/Composite/content/views/browser/browser.aspx +++ b/Website/Composite/content/views/browser/browser.aspx @@ -49,7 +49,7 @@ - + diff --git a/Website/Composite/localization/Composite.Management.en-us.xml b/Website/Composite/localization/Composite.Management.en-us.xml index 552404f109..e5ce9ce36d 100644 --- a/Website/Composite/localization/Composite.Management.en-us.xml +++ b/Website/Composite/localization/Composite.Management.en-us.xml @@ -664,6 +664,7 @@ + From 44b1f456d222b62f7984b07c551e4e6fcc9b38be Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Wed, 31 Aug 2016 17:39:41 +0200 Subject: [PATCH 213/213] Updating version number to 5.2 --- Composite.Workflows/Properties/AssemblyInfo.cs | 2 +- Composite/Properties/AssemblyInfo.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Composite.Workflows/Properties/AssemblyInfo.cs b/Composite.Workflows/Properties/AssemblyInfo.cs index 8e8883acfa..ce7907a1c0 100644 --- a/Composite.Workflows/Properties/AssemblyInfo.cs +++ b/Composite.Workflows/Properties/AssemblyInfo.cs @@ -32,6 +32,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.1.*")] +[assembly: AssemblyVersion("5.2.*")] [assembly: InternalsVisibleTo("UpgradePackage")] \ No newline at end of file diff --git a/Composite/Properties/AssemblyInfo.cs b/Composite/Properties/AssemblyInfo.cs index c9d5f3b96b..8bfac26e19 100644 --- a/Composite/Properties/AssemblyInfo.cs +++ b/Composite/Properties/AssemblyInfo.cs @@ -8,9 +8,9 @@ // associated with an assembly. #if !InternalBuild -[assembly: AssemblyTitle("Composite C1 5.1")] +[assembly: AssemblyTitle("Composite C1 5.2")] #else -[assembly: AssemblyTitle("Composite C1 5.1 (Internal Build)")] +[assembly: AssemblyTitle("Composite C1 5.2 (Internal Build)")] #endif [assembly: AssemblyDescription("Composite C1 Core classes")] @@ -39,7 +39,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("5.1.*")] +[assembly: AssemblyVersion("5.2.*")] [assembly: InternalsVisibleTo("UpgradePackage")] [assembly: InternalsVisibleTo("Composite.Workflows")] \ No newline at end of file