diff --git a/.gitignore b/.gitignore index fb9ec3b3b4..95b556a942 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ - + /.vs /Composite/bin /Composite/obj @@ -36,3 +36,5 @@ /Packages/ selenium-debug.log /Website/test/e2e/reports/ + +GitCommitInfo.cs \ No newline at end of file diff --git a/Composite.Workflows/C1Console/Scheduling/BaseSchedulerWorkflow.cs b/Composite.Workflows/C1Console/Scheduling/BaseSchedulerWorkflow.cs index b7a89eb4e3..f633bdb92b 100644 --- a/Composite.Workflows/C1Console/Scheduling/BaseSchedulerWorkflow.cs +++ b/Composite.Workflows/C1Console/Scheduling/BaseSchedulerWorkflow.cs @@ -39,6 +39,7 @@ private void initializeCodeActivity_ExecuteCode(object sender, EventArgs e) private void finalizeCodeActivity_ExecuteCode(object sender, EventArgs e) { using (ThreadDataManager.Initialize()) + using (ServiceLocator.EnsureThreadDataServiceScope()) { Execute(); } diff --git a/Composite.Workflows/C1Console/Scheduling/DataPublishSchedulerWorkflow.cs b/Composite.Workflows/C1Console/Scheduling/DataPublishSchedulerWorkflow.cs index c5fa9e2081..d8506b1756 100644 --- a/Composite.Workflows/C1Console/Scheduling/DataPublishSchedulerWorkflow.cs +++ b/Composite.Workflows/C1Console/Scheduling/DataPublishSchedulerWorkflow.cs @@ -36,8 +36,14 @@ protected override void Execute() var publishSchedule = PublishScheduleHelper.GetPublishSchedule(type, DataId, LocaleName); DataFacade.Delete(publishSchedule); - var data = (IPublishControlled)DataFacade.GetDataByUniqueKey(type, DataId); - Verify.IsNotNull(data, "The data with the id '{0}' does not exist", DataId); + var data = (IPublishControlled)DataFacade.TryGetDataByUniqueKey(type, DataId); + if (data == null) + { + Log.LogWarning(LogTitle, $"Failed to find data of type '{type}' by id '{DataId}'."); + + transaction.Complete(); + return; + } dataEntityToken = data.GetDataEntityToken(); @@ -49,11 +55,11 @@ protected override void Execute() DataFacade.Update(data); - Log.LogVerbose(LogTitle, "Scheduled publishing of data with label '{0}' is complete", data.GetLabel()); + Log.LogVerbose(LogTitle, $"Scheduled publishing of data with label '{data.GetLabel()}' is complete"); } else { - Log.LogWarning(LogTitle, "Scheduled publishing of data with label '{0}' could not be done because the data is not in a publisheble state", data.GetLabel()); + Log.LogWarning(LogTitle, $"Scheduled publishing of data with label '{data.GetLabel()}' could not be done because the data is not in a publisheble state"); } transaction.Complete(); diff --git a/Composite.Workflows/C1Console/Scheduling/DataUnpublishSchedulerWorkflow.cs b/Composite.Workflows/C1Console/Scheduling/DataUnpublishSchedulerWorkflow.cs index d9be03a759..1dedbee6e5 100644 --- a/Composite.Workflows/C1Console/Scheduling/DataUnpublishSchedulerWorkflow.cs +++ b/Composite.Workflows/C1Console/Scheduling/DataUnpublishSchedulerWorkflow.cs @@ -37,10 +37,16 @@ protected override void Execute() DataFacade.Delete(unpublishSchedule); - var deletePublished = false; + var data = (IPublishControlled)DataFacade.TryGetDataByUniqueKey(type, DataId); + if (data == null) + { + Log.LogWarning(LogTitle, $"Failed to find data of type '{type}' by id '{DataId}'."); - var data = (IPublishControlled)DataFacade.GetDataByUniqueKey(type, DataId); - Verify.IsNotNull(data, "The data with the id {0} does not exist", DataId); + transaction.Complete(); + return; + } + + var deletePublished = false; dataEntityToken = data.GetDataEntityToken(); @@ -56,7 +62,7 @@ protected override void Execute() } else { - Log.LogWarning(LogTitle, "Scheduled unpublishing of data with label '{0}' could not be done because the data is not in a unpublisheble state", data.GetLabel()); + Log.LogWarning(LogTitle, $"Scheduled unpublishing of data with label '{data.GetLabel()}' could not be done because the data is not in a unpublisheble state"); } @@ -69,7 +75,7 @@ protected override void Execute() { DataFacade.Delete(deletedData, CascadeDeleteType.Disable); - Log.LogVerbose(LogTitle, "Scheduled unpublishing of data with label '{0}' is complete", deletedData.GetLabel()); + Log.LogVerbose(LogTitle, $"Scheduled unpublishing of data with label '{deletedData.GetLabel()}' is complete"); } } } diff --git a/Composite.Workflows/Composite.Workflows.csproj b/Composite.Workflows/Composite.Workflows.csproj index ccb14bf104..178935dd0e 100644 --- a/Composite.Workflows/Composite.Workflows.csproj +++ b/Composite.Workflows/Composite.Workflows.csproj @@ -11,7 +11,7 @@ Composite Composite.Workflows {14822709-B5A1-4724-98CA-57A101D1B079};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - v4.6.1 + v4.7.1 512 SAK SAK @@ -965,6 +965,7 @@ ChangeOwnPasswordWorkflow.cs + @@ -1362,11 +1363,26 @@ + + + $(ProjectDir)git_branch.txt + $(ProjectDir)git_commithash.txt + + + + + $([System.IO.File]::ReadAllText("$(GitBranchFile)").Trim()) + $([System.IO.File]::ReadAllText("$(GitCommitHashFile)").Trim()) + [assembly: System.Reflection.AssemblyInformationalVersion("$(GitBranch). Commit Hash: $(GitCommitHash)")] + + + + + + copy "$(TargetPath)" "$(ProjectDir)..\bin\" copy "$(TargetPath)" "$(ProjectDir)..\Website\bin\" diff --git a/Composite/C1Console/Security/Foundation/PluginFacades/LoginSessionStorePluginFacade.cs b/Composite/C1Console/Security/Foundation/PluginFacades/LoginSessionStorePluginFacade.cs index e0143d3290..32bc26763d 100644 --- a/Composite/C1Console/Security/Foundation/PluginFacades/LoginSessionStorePluginFacade.cs +++ b/Composite/C1Console/Security/Foundation/PluginFacades/LoginSessionStorePluginFacade.cs @@ -26,8 +26,7 @@ static LoginSessionStorePluginFacade() public static bool HasConfiguration() { - return (ConfigurationServices.ConfigurationSource != null) && - (ConfigurationServices.ConfigurationSource.GetSection(LoginSessionStoreSettings.SectionName) != null); + return ConfigurationServices.ConfigurationSource?.GetSection(LoginSessionStoreSettings.SectionName) != null; } @@ -53,26 +52,23 @@ public static void StoreUsername(string userName, bool persistAcrossSessions) } - public static void FlushUsername() + public static string Logout() { using (_resourceLocker.ReadLocker) { - _resourceLocker.Resources.Provider.FlushUsername(); - } - } + var provider = _resourceLocker.Resources.Provider; + provider.FlushUsername(); - - public static string StoredUsername - { - get - { - return _resourceLocker.Resources.Provider.StoredUsername; + return (provider as ILoginSessionStoreRedirectedLogout)?.LogoutUrl; } } + public static string StoredUsername => _resourceLocker.Resources.Provider.StoredUsername; + + public static IPAddress UserIpAddress { get @@ -97,15 +93,15 @@ private static void HandleConfigurationError(Exception ex) { Flush(); - throw new ConfigurationErrorsException(string.Format("Failed to load the configuration section '{0}' from the configuration.", LoginSessionStoreSettings.SectionName), ex); + throw new ConfigurationErrorsException($"Failed to load the configuration section '{LoginSessionStoreSettings.SectionName}' from the configuration.", ex); } private sealed class Resources { - public LoginSessionStoreFactory Factory { get; set; } - public ILoginSessionStore Provider { get; set; } + private LoginSessionStoreFactory Factory { get; set; } + public ILoginSessionStore Provider { get; private set; } public static void DoInitializeResources(Resources resources) { @@ -127,7 +123,7 @@ public static void DoInitializeResources(Resources resources) } else if (RuntimeInformation.IsUnittest) { - // This is a fall bakc for unittests + // This is a fallback for unittests resources.Provider = new BuildinLoginSessionStore(); } } diff --git a/Composite/C1Console/Security/Plugins/LoginSessionStore/ILoginSessionStoreRedirectedLogout.cs b/Composite/C1Console/Security/Plugins/LoginSessionStore/ILoginSessionStoreRedirectedLogout.cs new file mode 100644 index 0000000000..35fb52f3d5 --- /dev/null +++ b/Composite/C1Console/Security/Plugins/LoginSessionStore/ILoginSessionStoreRedirectedLogout.cs @@ -0,0 +1,11 @@ +namespace Composite.C1Console.Security.Plugins.LoginSessionStore +{ + /// + /// Allows specifying a logout URL. + /// + public interface ILoginSessionStoreRedirectedLogout + { + /// + string LogoutUrl { get; } + } +} diff --git a/Composite/C1Console/Security/Plugins/LoginSessionStore/Runtime/LoginSessionStoreResolver.cs b/Composite/C1Console/Security/Plugins/LoginSessionStore/Runtime/LoginSessionStoreResolver.cs index eecaafb1a4..195863827b 100644 --- a/Composite/C1Console/Security/Plugins/LoginSessionStore/Runtime/LoginSessionStoreResolver.cs +++ b/Composite/C1Console/Security/Plugins/LoginSessionStore/Runtime/LoginSessionStoreResolver.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -6,7 +6,7 @@ namespace Composite.C1Console.Security.Plugins.LoginSessionStore.Runtime { - internal class LoginSessionStoreResolver : ILoginSessionStore + internal class LoginSessionStoreResolver : ILoginSessionStore, ILoginSessionStoreRedirectedLogout { private readonly IEnumerable _loginSessionStores; @@ -36,5 +36,10 @@ public void FlushUsername() } public IPAddress UserIpAddress => PreferredLoginSessionStore()?.UserIpAddress; + + public string LogoutUrl => _loginSessionStores + .OfType() + .Select(_ => _.LogoutUrl) + .FirstOrDefault(url => !string.IsNullOrEmpty(url)); } } \ No newline at end of file diff --git a/Composite/C1Console/Security/UserValidationFacade.cs b/Composite/C1Console/Security/UserValidationFacade.cs index 158f2e07f5..8f2e89d58c 100644 --- a/Composite/C1Console/Security/UserValidationFacade.cs +++ b/Composite/C1Console/Security/UserValidationFacade.cs @@ -1,6 +1,5 @@ using System; using System.Web; -using Composite.Core.Extensions; using Composite.Core.Logging; using Composite.C1Console.Security.Foundation.PluginFacades; using Composite.C1Console.Security.Plugins.LoginProvider; @@ -54,29 +53,17 @@ public static ValidationType GetValidationType() return ValidationType.Windows; } - throw new InvalidOperationException(string.Format("Validation plugin '{0}' does not implement a known validation interface", LoginProviderPluginFacade.GetValidationPluginType())); + throw new InvalidOperationException($"Validation plugin '{LoginProviderPluginFacade.GetValidationPluginType()}' does not implement a known validation interface"); } /// - public static IEnumerable AllUsernames - { - get - { - return LoginProviderPluginFacade.AllUsernames; - } - } + public static IEnumerable AllUsernames => LoginProviderPluginFacade.AllUsernames; /// - public static bool CanSetUserPassword - { - get - { - return LoginProviderPluginFacade.CanSetUserPassword; - } - } + public static bool CanSetUserPassword => LoginProviderPluginFacade.CanSetUserPassword; /// @@ -106,7 +93,7 @@ public static LoginResult FormValidateUser(string userName, string password) if (loginResult == LoginResult.Success) { - LoggingService.LogVerbose("UserValidation", String.Format("The user: [{0}], has been validated and accepted. {1}", userName, GetIpInformation()), LoggingService.Category.Audit); + LoggingService.LogVerbose("UserValidation", $"The user: [{userName}], has been validated and accepted. {GetIpInformation()}", LoggingService.Category.Audit); PersistUsernameInSessionDataProvider(userName); } else if (LoginResultDescriptions.ContainsKey(loginResult)) @@ -120,7 +107,7 @@ public static LoginResult FormValidateUser(string userName, string password) private static void LogLoginFailed(string userName, string message) { LoggingService.LogWarning("UserValidation", - "Login as [{0}] failed. {1} {2}".FormatWith(userName, message, GetIpInformation()), + $"Login as [{userName}] failed. {message} {GetIpInformation()}", LoggingService.Category.Audit); } @@ -145,7 +132,7 @@ private static string GetIpInformation() return string.Empty; } - return " IP address: " + ipaddress; + return "IP address: " + ipaddress; } @@ -185,20 +172,21 @@ public static bool WindowsValidateUser(string userName, string domainName) /// - /// Flushes the username from the login session + /// Flushes the username from the login session. /// - public static void Logout() - { - LoginSessionStorePluginFacade.FlushUsername(); - } + [Obsolete("Use Logout() instead.")] + public static void FlushUsername() => Logout(); + + + /// + /// Flushes the username from the login session (if applicable) and returns a logout URL. + /// + public static string Logout() => LoginSessionStorePluginFacade.Logout(); /// - public static bool IsLoggedIn() - { - return !string.IsNullOrEmpty(LoginSessionStorePluginFacade.StoredUsername); - } + public static bool IsLoggedIn() => !string.IsNullOrEmpty(LoginSessionStorePluginFacade.StoredUsername); diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 357a785b4c..385e2e52ca 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -11,7 +11,7 @@ Composite {14822709-B5A1-4724-98CA-57A101D1B079};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 - v4.6.1 + v4.7.1 SAK SAK SAK @@ -125,14 +125,6 @@ - - ..\packages\System.Net.WebSockets.4.0.0\lib\net46\System.Net.WebSockets.dll - True - - - ..\packages\System.Net.WebSockets.Client.4.0.0\lib\net46\System.Net.WebSockets.Client.dll - True - ..\packages\System.Reactive.Core.3.0.0\lib\net46\System.Reactive.Core.dll True @@ -163,10 +155,6 @@ 3.5 - - False - ..\Packages\System.ValueTuple.4.3.0\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll - ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll @@ -245,11 +233,13 @@ + + @@ -263,6 +253,7 @@ + @@ -2677,11 +2668,26 @@ + + + $(ProjectDir)git_branch.txt + $(ProjectDir)git_commithash.txt + + + + + $([System.IO.File]::ReadAllText("$(GitBranchFile)").Trim()) + $([System.IO.File]::ReadAllText("$(GitCommitHashFile)").Trim()) + [assembly: System.Reflection.AssemblyInformationalVersion("$(GitBranch). Commit Hash: $(GitCommitHash)")] + + + + + + copy "$(TargetPath)" "$(ProjectDir)..\bin\" copy "$(TargetPath)" "$(ProjectDir)..\Website\bin\" diff --git a/Composite/Core/EmptyDisposable.cs b/Composite/Core/EmptyDisposable.cs new file mode 100644 index 0000000000..d1af318d77 --- /dev/null +++ b/Composite/Core/EmptyDisposable.cs @@ -0,0 +1,13 @@ +using System; + +namespace Composite.Core +{ + internal class EmptyDisposable : IDisposable + { + public static readonly EmptyDisposable Instance = new EmptyDisposable(); + + public void Dispose() + { + } + } +} diff --git a/Composite/Core/IO/Zip/ZipFileSystem.cs b/Composite/Core/IO/Zip/ZipFileSystem.cs index b3086abfb5..5693138c1d 100644 --- a/Composite/Core/IO/Zip/ZipFileSystem.cs +++ b/Composite/Core/IO/Zip/ZipFileSystem.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -11,7 +11,11 @@ internal sealed class ZipFileSystem : IZipFileSystem private const int CopyBufferSize = 4096; private readonly HashSet _entryNames = new HashSet(); - private string ZipFilename { get; set; } + private readonly Dictionary _denormalizedEntryNames = new Dictionary(); + + private string ZipFilename { get; } + + private static string NormalizePathDelimiters(string path) => path.Replace('\\', '/'); public ZipFileSystem(string zipFilename) { @@ -26,7 +30,7 @@ public ZipFileSystem(string zipFilename) public bool ContainsFile(string filename) { - filename = filename.Replace('\\', '/'); + filename = NormalizePathDelimiters(filename); return GetFilenames().Any(f => f.Equals(filename, StringComparison.OrdinalIgnoreCase)); } @@ -35,7 +39,7 @@ public bool ContainsFile(string filename) public bool ContainsDirectory(string directoryName) { - directoryName = directoryName.Replace('\\', '/'); + directoryName = NormalizePathDelimiters(directoryName); return GetDirectoryNames().Any(f => f.Equals(directoryName, StringComparison.OrdinalIgnoreCase)); } @@ -54,7 +58,7 @@ public IEnumerable GetFilenames() public IEnumerable GetFilenames(string directoryName) { - directoryName = directoryName.Replace('\\', '/'); + directoryName = NormalizePathDelimiters(directoryName); foreach (var filename in GetFilenames()) { @@ -109,14 +113,20 @@ public Stream GetFileStream(string filename) var zipArchive = new ZipArchive(C1File.Open(ZipFilename, FileMode.Open, FileAccess.Read)); - var entryPath = filename.Substring(2).Replace('\\', '/'); + + var normalizedEntryPath = NormalizePathDelimiters(filename.Substring(2)); + + if(!_denormalizedEntryNames.TryGetValue(normalizedEntryPath, out string entryPath)) + { + throw new InvalidOperationException($"Entry '{normalizedEntryPath}' not found"); + } var entry = zipArchive.GetEntry(entryPath); if (entry == null) { zipArchive.Dispose(); - throw new InvalidOperationException($"Entry '{entryPath}' not found"); + throw new InvalidOperationException($"Failed to extract entry '{entryPath}' from zip archive"); } return new StreamWrapper(entry.Open(), () => zipArchive.Dispose()); @@ -162,7 +172,10 @@ private void Initialize() { foreach (var entry in zipArchive.Entries) { - _entryNames.Add(entry.FullName); + var normalizedEntryPath = NormalizePathDelimiters(entry.FullName); + _denormalizedEntryNames[normalizedEntryPath] = entry.FullName; + + _entryNames.Add(normalizedEntryPath); } } } @@ -177,17 +190,14 @@ private static string ParseFilename(string filename) throw new ArgumentException("filename should start with a '~/' or '~\\'"); } - filename = filename.Remove(0, 1); - filename = filename.Replace('\\', '/'); + filename = NormalizePathDelimiters(filename.Substring(1)); if (!filename.StartsWith("/")) { throw new ArgumentException("filename should start with a '~/' or '~\\'"); } - filename = filename.Remove(0, 1); - - return filename; + return filename.Substring(1); } private class StreamWrapper : Stream, IDisposable diff --git a/Composite/Core/Implementation/DataConnectionImplementation.cs b/Composite/Core/Implementation/DataConnectionImplementation.cs index 4c0ac58939..254bc32c6d 100644 --- a/Composite/Core/Implementation/DataConnectionImplementation.cs +++ b/Composite/Core/Implementation/DataConnectionImplementation.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.Linq; using Composite.Core.Threading; @@ -15,6 +14,7 @@ namespace Composite.Core.Implementation public class DataConnectionImplementation : DataConnectionBase, IDisposable { private IDisposable _threadDataManager; + private readonly IDisposable _serviceScope; private readonly DataScope _dataScope; internal DataScope DataScope => _dataScope; @@ -31,6 +31,8 @@ public DataConnectionImplementation(PublicationScope scope, CultureInfo locale) InitializeScope(scope, locale); _dataScope = new DataScope(this.DataScopeIdentifier, locale); + + _serviceScope = ServiceLocator.EnsureThreadDataServiceScope(); } private void InitializeThreadData() @@ -198,6 +200,8 @@ protected virtual void Dispose(bool disposing) { if (disposing) { + _serviceScope?.Dispose(); + _dataScope.Dispose(); _threadDataManager.Dispose(); diff --git a/Composite/Core/Instrumentation/Profiler.cs b/Composite/Core/Instrumentation/Profiler.cs index 6883483895..18cbd3f35e 100644 --- a/Composite/Core/Instrumentation/Profiler.cs +++ b/Composite/Core/Instrumentation/Profiler.cs @@ -1,4 +1,4 @@ -#define ProfileMemory +#define ProfileMemory using System; using System.Collections.Generic; @@ -227,18 +227,6 @@ internal static void AddSubMeasurement(Measurement measurement) } } - private static bool Disabled - { - get { return false; } - } - - private class EmptyDisposable: IDisposable - { - public static readonly EmptyDisposable Instance = new EmptyDisposable(); - - public void Dispose() - { - } - } + private static bool Disabled => false; } } diff --git a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs index c0024d080b..38d299cb77 100644 --- a/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs +++ b/Composite/Core/PackageSystem/PackageFragmentInstallers/DataPackageFragmentInstaller.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -32,6 +32,8 @@ public sealed class DataPackageFragmentInstaller : BasePackageFragmentInstaller private List _validationResult; private Dictionary _dataKeysToBeInstalled; + private Dictionary _dataKeysToBeInstalledByTypeId; + private Dictionary>> _missingDataReferences; private static Dictionary _pageVersionIds = new Dictionary(); @@ -41,6 +43,7 @@ public override IEnumerable Validate() { _validationResult = new List(); _dataKeysToBeInstalled = new Dictionary(); + _dataKeysToBeInstalledByTypeId = new Dictionary(); _missingDataReferences = new Dictionary>>(); if (this.Configuration.Count(f => f.Name == "Types") > 1) @@ -597,7 +600,7 @@ private void ValidateNonDynamicAddedType(DataType dataType) } - RegisterKeyToBeAdded(dataType, dataKeyPropertyCollection); + RegisterKeyToBeAdded(dataType, null, dataKeyPropertyCollection); // Checking foreign key references foreach (var foreignKeyProperty in DataAttributeFacade.GetDataReferenceProperties(dataType.InterfaceType)) @@ -632,16 +635,24 @@ private void CheckForBrokenReference(DataType refereeType, Type type, string pro // Checking key in the keys to be installed var keyValuePair = new KeyValuePair(keyPropertyName, referenceKey); - if (_missingDataReferences.ContainsKey(referredType) && _missingDataReferences[referredType].Contains(keyValuePair)) + if (_missingDataReferences.TryGetValue(referredType, out var refs) && refs.Contains(keyValuePair)) { return; } - if (_dataKeysToBeInstalled.ContainsKey(referredType) && _dataKeysToBeInstalled[referredType].KeyRegistered(refereeType, keyValuePair)) + if (_dataKeysToBeInstalled.TryGetValue(referredType, out var keys) + && keys.KeyRegistered(refereeType, keyValuePair)) { return; } + var typeId = referredType.GetImmutableTypeId(); + if (_dataKeysToBeInstalledByTypeId.TryGetValue(typeId, out var dynamicTypeKeys) + && dynamicTypeKeys.KeyRegistered(refereeType, keyValuePair)) + { + return; + } + using (GetDataScopeFromDataTypeElement(refereeType)) { if (DataFacade.TryGetDataByUniqueKey(type, propertyValue) == null) @@ -705,13 +716,26 @@ private DataScope GetDataScopeFromDataTypeElement(DataType dataType) return new DataScope(dataType.DataScopeIdentifier, locale); } - private void RegisterKeyToBeAdded(DataType dataType, DataKeyPropertyCollection dataKeyPropertyCollection) + + + private void RegisterKeyToBeAdded(DataType dataType, DataTypeDescriptor dataTypeDescriptor, DataKeyPropertyCollection dataKeyPropertyCollection) { if (dataKeyPropertyCollection.Count != 1) return; + TypeKeyInstallationData typeKeyInstallationData; - var typeKeyInstallationData = _dataKeysToBeInstalled.GetOrAdd(dataType.InterfaceType, + if (dataType.InterfaceType != null) + { + // Static types + typeKeyInstallationData = _dataKeysToBeInstalled.GetOrAdd(dataType.InterfaceType, () => new TypeKeyInstallationData(dataType.InterfaceType)); + } + else + { + // Dynamic types + typeKeyInstallationData = _dataKeysToBeInstalledByTypeId.GetOrAdd(dataTypeDescriptor.DataTypeId, + () => new TypeKeyInstallationData(dataTypeDescriptor)); + } var keyValuePair = dataKeyPropertyCollection.KeyProperties.First(); @@ -791,8 +815,7 @@ private void ValidateDynamicAddedType(DataType dataType) // TODO: implement check if the same key has already been added } - // TODO: to be implemented for dynamic types - // RegisterKeyToBeAdded(dataType, dataKeyPropertyCollection); + RegisterKeyToBeAdded(dataType, dataTypeDescriptor, dataKeyPropertyCollection); // Checking foreign key references foreach (var referenceField in dataTypeDescriptor.Fields.Where(f => f.ForeignKeyReferenceTypeName != null)) @@ -800,8 +823,8 @@ private void ValidateDynamicAddedType(DataType dataType) object propertyValue; if (!fieldValues.TryGetValue(referenceField.Name, out propertyValue) || propertyValue == null - || (propertyValue is Guid && (Guid)propertyValue == Guid.Empty) - || propertyValue is string && (string)propertyValue == "") + || (propertyValue is Guid guid && guid == Guid.Empty) + || (propertyValue is string str && str == "")) { continue; } @@ -858,21 +881,29 @@ private sealed class DataType /// /// Information about data keys to be installed for a given data type /// - [DebuggerDisplay("TypeKeyInstallationData {_type.FullName} . DataScopes: {_dataScopes.Count}")] + [DebuggerDisplay("TypeKeyInstallationData {_typeName} . DataScopes: {_dataScopes.Count}")] private class TypeKeyInstallationData { private const string AllLocalesKey = "all"; private readonly bool _isLocalized; private readonly bool _isPublishable; - private readonly Type _type; + private readonly string _typeName; + private readonly Dictionary>> _dataScopes = new Dictionary>>(); public TypeKeyInstallationData(Type type) { _isLocalized = DataLocalizationFacade.IsLocalized(type); _isPublishable = typeof(IPublishControlled).IsAssignableFrom(type); - _type = type; + _typeName = type.FullName; + } + + public TypeKeyInstallationData(DataTypeDescriptor typeDescriptor) + { + _isLocalized = typeDescriptor.SuperInterfaces.Contains(typeof(ILocalizedControlled)); + _isPublishable = typeDescriptor.SuperInterfaces.Contains(typeof(IPublishControlled)); + _typeName = typeDescriptor.Name; } public void RegisterKeyUsage(DataType dataType, KeyValuePair keyValuePair) @@ -910,15 +941,10 @@ private void RegisterKeyUsage(DataType dataType, string localeName, DataScopeIde { string dataScopeKey = GetDataScopeKey(publicationScope, localeName); - if (!_dataScopes.ContainsKey(dataScopeKey)) - { - _dataScopes.Add(dataScopeKey, new HashSet>()); - } - - var hashset = _dataScopes[dataScopeKey]; + var hashset = _dataScopes.GetOrAdd(dataScopeKey, () => new HashSet>()); Verify.That(!hashset.Contains(keyValuePair), "Item with the same key present twice. Data type: '{0}', field '{1}', value '{2}'", - dataType.InterfaceTypeName ?? "null", keyValuePair.Key, keyValuePair.Value ?? "null"); + dataType.InterfaceTypeName ?? dataType.InterfaceTypeName ?? "null", keyValuePair.Key, keyValuePair.Value ?? "null"); hashset.Add(keyValuePair); } diff --git a/Composite/Core/ServiceLocator.cs b/Composite/Core/ServiceLocator.cs index 871ef6848d..654d3a6e47 100644 --- a/Composite/Core/ServiceLocator.cs +++ b/Composite/Core/ServiceLocator.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Configuration; using Composite.Core.Configuration; +using Composite.Core.Threading; using Microsoft.Extensions.DependencyInjection; namespace Composite.Core @@ -20,6 +21,7 @@ namespace Composite.Core public static class ServiceLocator { private const string HttpContextKey = "HttpApplication.ServiceScope"; + private const string ThreadDataKey = "HttpApplication.ServiceScope"; private static IServiceCollection _serviceCollection = new ServiceCollection(); private static IServiceProvider _serviceProvider; private static ConcurrentDictionary _hasTypeLookup = new ConcurrentDictionary(); @@ -213,9 +215,23 @@ internal static void DisposeRequestServicesScope(HttpContext context) if (scope != null) { scope.Dispose(); + context.Items.Remove(HttpContextKey); } } + internal static IDisposable EnsureThreadDataServiceScope() + { + if (RequestScopedServiceProvider != null) return EmptyDisposable.Instance; + + var current = ThreadDataManager.GetCurrentNotNull(); + + var serviceScopeFactory = (IServiceScopeFactory)_serviceProvider.GetService(typeof(IServiceScopeFactory)); + var serviceScope = serviceScopeFactory.CreateScope(); + + current.SetValue(ThreadDataKey, serviceScope); + + return new ThreadDataServiceScopeDisposable(current); + } /// /// Return a IServiceScope - if a scope has been initialized on the request (HttpContext) a scoped provider is returned. @@ -225,11 +241,39 @@ private static IServiceProvider RequestScopedServiceProvider get { var context = HttpContext.Current; - if (context == null) return null; + if (context != null) + { + var scope = (IServiceScope)context.Items[HttpContextKey]; - var scope = (IServiceScope)context.Items[HttpContextKey]; + return scope?.ServiceProvider; + } + + var threadData = ThreadDataManager.Current; + if (threadData != null) + { + var scope = (IServiceScope) threadData[ThreadDataKey]; + return scope?.ServiceProvider; + } + + return null; + } + } + + private class ThreadDataServiceScopeDisposable : IDisposable + { + private readonly ThreadDataManagerData _threadData; + + public ThreadDataServiceScopeDisposable(ThreadDataManagerData threadData) + { + _threadData = threadData; + } + + public void Dispose() + { + var scope = (IServiceScope)_threadData[ThreadDataKey]; - return scope != null ? scope.ServiceProvider : null; + scope?.Dispose(); + _threadData.SetValue(ThreadDataKey, null); } } diff --git a/Composite/Core/Threading/ThreadDataManager.cs b/Composite/Core/Threading/ThreadDataManager.cs index ae2c530a4c..b338e75cba 100644 --- a/Composite/Core/Threading/ThreadDataManager.cs +++ b/Composite/Core/Threading/ThreadDataManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; using Composite.C1Console.Security.Foundation.PluginFacades; @@ -132,7 +132,7 @@ public static IDisposable Initialize(ThreadDataManagerData parentThreadData) /// An scope public static IDisposable EnsureInitialize() { - if (Current != null) return new EmptyDisposableObj(); + if (Current != null) return EmptyDisposable.Instance; return Initialize(); } @@ -207,15 +207,6 @@ public static void FinalizeThroughHttpContext() } - private sealed class EmptyDisposableObj : IDisposable - { - public void Dispose() - { - // Do nothing here... - } - - } - private sealed class ThreadDataManagerScope : IDisposable { private bool _disposed; diff --git a/Composite/Core/WebClient/Captcha/CaptchaConfiguration.cs b/Composite/Core/WebClient/Captcha/CaptchaConfiguration.cs index ac630c9367..6bdf07258b 100644 --- a/Composite/Core/WebClient/Captcha/CaptchaConfiguration.cs +++ b/Composite/Core/WebClient/Captcha/CaptchaConfiguration.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Xml; using Composite.Core.Extensions; @@ -9,59 +9,54 @@ namespace Composite.Core.WebClient.Captcha internal static class CaptchaConfiguration { private static readonly string CaptchaConfigurationFilePath = @"App_Data\Composite\Configuration\Captcha.xml"; - private static readonly object _syncRoot = new object(); - private static string _password; + public static string Password { get; } - public static string Password + static CaptchaConfiguration() { - get - { - if (_password != null) return _password; - - lock (_syncRoot) - { - if (_password != null) return _password; + string configurationFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, CaptchaConfigurationFilePath); - string configurationFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, CaptchaConfigurationFilePath); + string password = null; - if (C1File.Exists(configurationFilePath)) + if (C1File.Exists(configurationFilePath)) + { + var doc = new XmlDocument(); + try + { + using (var sr = new C1StreamReader(configurationFilePath)) { - var doc = new XmlDocument(); - try - { - using (var sr = new C1StreamReader(configurationFilePath)) - { - doc.Load(sr); - } + doc.Load(sr); + } - var passwordNode = doc.SelectSingleNode("captcha/password"); - if (passwordNode != null && !string.IsNullOrEmpty(passwordNode.InnerText)) - { - _password = passwordNode.InnerText; - } - } - catch (Exception) - { - // Do nothing - } + var passwordNode = doc.SelectSingleNode("captcha/password"); + if (!string.IsNullOrEmpty(passwordNode?.InnerText)) + { + password = passwordNode.InnerText; + } + } + catch (Exception) + { + // Do nothing + } - if (_password != null) return _password; + if (password != null) + { + Password = password; + return; + } - // Deleting configuration file - C1File.Delete(configurationFilePath); - } + // Deleting configuration file + C1File.Delete(configurationFilePath); + } - _password = Guid.NewGuid().ToString(); + password = Guid.NewGuid().ToString(); - string configFile = @" {0} ".FormatWith(_password); + string configFile = @" {0} ".FormatWith(password); - C1File.WriteAllText(configurationFilePath, configFile); + C1File.WriteAllText(configurationFilePath, configFile); - return _password; - } - } + Password = password; } } } diff --git a/Composite/Core/WebClient/Captcha/Encryption.cs b/Composite/Core/WebClient/Captcha/Encryption.cs index dc0eded538..d725a15898 100644 --- a/Composite/Core/WebClient/Captcha/Encryption.cs +++ b/Composite/Core/WebClient/Captcha/Encryption.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text; -using System.Web.Hosting; +using Composite.Core.Configuration; using Composite.Core.IO; @@ -16,19 +16,25 @@ internal static class Encryption static Encryption() { - var md5 = MD5.Create(); - - string key = Environment.MachineName + CaptchaConfiguration.Password + HostingEnvironment.ApplicationPhysicalPath; + string key = InstallationInformationFacade.InstallationId + CaptchaConfiguration.Password; byte[] keyBytes = Encoding.UTF8.GetBytes(key); - _encryptionKey = md5.ComputeHash(keyBytes); + using (var hashAlgorithm = MD5.Create()) + { + _encryptionKey = hashAlgorithm.ComputeHash(keyBytes); + } } public static string Encrypt(string value) { Verify.ArgumentNotNullOrEmpty(value, nameof(value)); + return ByteToHexString(RijndaelEncrypt(value)); + } + + private static byte[] RijndaelEncrypt(string value) + { // Create a RijndaelManaged object // with the specified key and IV. using (var rima = new RijndaelManaged()) @@ -49,7 +55,7 @@ public static string Encrypt(string value) swEncrypt.Write(value); } // Return the encrypted bytes from the memory stream. - return ByteToHexString(msEncrypt.ToArray()); + return msEncrypt.ToArray(); } } } @@ -59,6 +65,11 @@ public static string Decrypt(string encryptedValue) Verify.ArgumentNotNullOrEmpty(encryptedValue, nameof(encryptedValue)); byte[] encodedSequence = HexStringToByteArray(encryptedValue); + return RijndaelDecrypt(encodedSequence); + } + + private static string RijndaelDecrypt(byte[] bytes) + { using (var rima = new RijndaelManaged()) { rima.Key = _encryptionKey; @@ -68,7 +79,7 @@ public static string Decrypt(string encryptedValue) ICryptoTransform decryptor = rima.CreateDecryptor(); // Create the streams used for decryption. - using (var msDecrypt = new MemoryStream(encodedSequence)) + using (var msDecrypt = new MemoryStream(bytes)) using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) using (var srDecrypt = new C1StreamReader(csDecrypt)) { diff --git a/Composite/Core/WebClient/Renderings/Page/IPageContentFilter.cs b/Composite/Core/WebClient/Renderings/Page/IPageContentFilter.cs index c919553581..91f9a6d7fa 100644 --- a/Composite/Core/WebClient/Renderings/Page/IPageContentFilter.cs +++ b/Composite/Core/WebClient/Renderings/Page/IPageContentFilter.cs @@ -17,7 +17,7 @@ public interface IPageContentFilter void Filter(XhtmlDocument document, IPage page); /// - /// Gets the execution order. Filters wil lower values will be executed first. + /// Gets the execution order. Filters with lower values will be executed first. /// int Order { get; } } diff --git a/Composite/Data/PageManager.cs b/Composite/Data/PageManager.cs index 30d81e121f..32f2565d45 100644 --- a/Composite/Data/PageManager.cs +++ b/Composite/Data/PageManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -197,21 +197,24 @@ public static ReadOnlyCollection GetPlaceholderContent( public static ReadOnlyCollection GetPlaceholderContent(Guid pageId, Guid versionId) { string cacheKey = GetCacheKey(pageId, versionId); - var cachedValue = _placeholderCache.Get(cacheKey); - if (cachedValue != null) + var result = _placeholderCache.Get(cacheKey); + + if (result == null) { - return cachedValue; - } + using (var conn = new DataConnection()) + { + conn.DisableServices(); - var list = DataFacade.GetData(false) - .Where(f => f.PageId == pageId - && f.VersionId == versionId).ToList(); + var list = DataFacade.GetData(false) + .Where(f => f.PageId == pageId && f.VersionId == versionId).ToList(); - var readonlyList = new ReadOnlyCollection(list); + result = new ReadOnlyCollection(list); + } - _placeholderCache.Add(cacheKey, readonlyList); + _placeholderCache.Add(cacheKey, result); + } - return readonlyList; + return result; } #endregion Public diff --git a/Composite/Properties/SharedAssemblyInfo.cs b/Composite/Properties/SharedAssemblyInfo.cs index d6d0f99ded..10265634b6 100644 --- a/Composite/Properties/SharedAssemblyInfo.cs +++ b/Composite/Properties/SharedAssemblyInfo.cs @@ -2,9 +2,9 @@ // General Information about the assemblies Composite and Composite.Workflows #if !InternalBuild -[assembly: AssemblyTitle("C1 CMS 6.3")] +[assembly: AssemblyTitle("C1 CMS 6.4")] #else -[assembly: AssemblyTitle("C1 CMS 6.3 (Internal Build)")] +[assembly: AssemblyTitle("C1 CMS 6.4 (Internal Build)")] #endif [assembly: AssemblyCompany("Orckestra Inc")] @@ -13,4 +13,4 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("6.3.*")] +[assembly: AssemblyVersion("6.4.*")] diff --git a/Composite/Search/DocumentSources/CmsPageDocumentSource.cs b/Composite/Search/DocumentSources/CmsPageDocumentSource.cs index a918ecab7d..e38a3ca404 100644 --- a/Composite/Search/DocumentSources/CmsPageDocumentSource.cs +++ b/Composite/Search/DocumentSources/CmsPageDocumentSource.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -49,7 +49,8 @@ public CmsPageDocumentSource(IEnumerable extens var entityToken = GetAdministratedEntityToken(page); return entityToken != null ? FromPage(page, entityToken, null) : null; }, - data => GetDocumentId((IPage) data)); + data => GetDocumentId((IPage) data), + PageShouldBeIndexed); _changesIndexNotifier.Start(); } @@ -64,12 +65,14 @@ public void Subscribe(IDocumentSourceListener sourceListener) public IEnumerable GetSearchDocuments(CultureInfo culture, string continuationToken = null) { ICollection unpublishedPages; + IDictionary parentPageIDs; var (lastPageId, lastPagesPublicationScope) = ParseContinuationToken(continuationToken); using (var conn = new DataConnection(PublicationScope.Unpublished, culture)) { unpublishedPages = conn.Get().Evaluate(); + parentPageIDs = conn.Get().ToDictionary(ps => ps.Id, ps => ps.ParentId); } unpublishedPages = unpublishedPages @@ -78,9 +81,12 @@ public IEnumerable GetSearchDocuments(CultureInfo .ToList(); var publishedPages = new Dictionary, IPage>(); + HashSet publishedPageIds; + using (var conn = new DataConnection(PublicationScope.Published, culture)) { publishedPages = conn.Get().ToDictionary(page => new Tuple(page.Id, page.VersionId)); + publishedPageIds = new HashSet(publishedPages.Select(p => p.Key.Item1)); } var unpublishedMetaData = GetAllMetaData(PublicationScope.Unpublished, culture); @@ -89,12 +95,13 @@ public IEnumerable GetSearchDocuments(CultureInfo foreach (var unpublishedPage in unpublishedPages) { + Guid pageId = unpublishedPage.Id; var entityToken = unpublishedPage.GetDataEntityToken(); - IPage publishedPage; - if (unpublishedPage.Id.CompareTo(lastPageId) > 0 - && publishedPages.TryGetValue(new Tuple(unpublishedPage.Id, unpublishedPage.VersionId), - out publishedPage)) + if (pageId.CompareTo(lastPageId) > 0 + && publishedPages.TryGetValue(new Tuple(pageId, unpublishedPage.VersionId), + out IPage publishedPage) + && AllAncestorPagesArePublished(pageId, publishedPageIds, parentPageIDs)) { yield return new DocumentWithContinuationToken { @@ -109,7 +116,7 @@ public IEnumerable GetSearchDocuments(CultureInfo } } - if (unpublishedPage.Id.CompareTo(lastPageId) > 0 + if (pageId.CompareTo(lastPageId) > 0 || lastPagesPublicationScope == PublicationScope.Published) { yield return new DocumentWithContinuationToken @@ -121,7 +128,7 @@ public IEnumerable GetSearchDocuments(CultureInfo } } - private (Guid lastPage , PublicationScope publicationScope) ParseContinuationToken(string continuationToken) + private (Guid lastPage, PublicationScope publicationScope) ParseContinuationToken(string continuationToken) { if (continuationToken == null) { @@ -262,5 +269,82 @@ private List GetMetaData(Guid pageId, Guid versionId, PublicationScope pu return result; } + + private bool AllAncestorPagesArePublished(Guid pageId, HashSet publishedPageIds, + IDictionary parentPageIDs) + { + int depth = 100; + + while (depth > 0) + { + if (!parentPageIDs.TryGetValue(pageId, out Guid parentPageID)) + { + // The the page is unreachable from the tree, no need to index it + return false; + } + + if (parentPageID == Guid.Empty) + { + return true; + } + + if (!publishedPageIds.Contains(parentPageID)) + { + return false; + } + + pageId = parentPageID; + depth--; + } + + Log.LogError(nameof(CmsPageDocumentSource), $"There's a loop in page hierarchy. Page ID: '{pageId}'"); + return false; + } + + private bool AllAncestorPagesArePublished(Guid pageId, CultureInfo locale) + { + using (var dc = new DataConnection(PublicationScope.Published, locale)) + { + dc.DisableServices(); + + int depth = 100; + + while (depth > 0) + { + var parentPageId = PageManager.GetParentId(pageId); + if (parentPageId == Guid.Empty) return true; + + var parentPage = PageManager.GetPageById(parentPageId, true); + if (parentPage == null) + { + return false; + } + + pageId = parentPageId; + depth--; + } + } + + Log.LogError(nameof(CmsPageDocumentSource), $"There's a loop in page hierarchy. Page ID: '{pageId}'"); + return false; + } + + private bool PageShouldBeIndexed(IData data) + { + if (!(data is IPage page)) + { + return true; + } + + if (data.DataSourceId.PublicationScope == PublicationScope.Published) + { + return AllAncestorPagesArePublished(page.Id, page.DataSourceId.LocaleScope); + } + + // Indexing the unpublished version fo the page only if the page is not in the "published" state, + // or the published version isn't indexed + return page.PublicationStatus != GenericPublishProcessController.Published + || !AllAncestorPagesArePublished(page.Id, page.DataSourceId.LocaleScope); + } } } diff --git a/Composite/Search/DocumentSources/DataChangesIndexNotifier.cs b/Composite/Search/DocumentSources/DataChangesIndexNotifier.cs index ad8ed622cf..6f79096ab6 100644 --- a/Composite/Search/DocumentSources/DataChangesIndexNotifier.cs +++ b/Composite/Search/DocumentSources/DataChangesIndexNotifier.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -19,31 +19,34 @@ internal class DataChangesIndexNotifier private Func GetDocument { get; } private Func GetDocumentId { get; } + private Predicate DataItemShouldBeIndexed { get; } public DataChangesIndexNotifier( IEnumerable listeners, Type interfaceType, Func getDocumentFunc, - Func getDocumentIdFunc) + Func getDocumentIdFunc, + Predicate dataItemShouldBeIndexed = null) { _listeners = listeners; _interfaceType = interfaceType; GetDocument = getDocumentFunc; GetDocumentId = getDocumentIdFunc; + DataItemShouldBeIndexed = dataItemShouldBeIndexed ?? (_ => !IsPublishedDataFromUnpublishedScope(_)); } public void Start() { DataEventSystemFacade.SubscribeToDataAfterAdd(_interfaceType, - (sender, args) => GetActionContainer().Add(() => Data_OnAfterAdd(sender, args)), + (sender, args) => CatchAll(() => GetActionContainer().Add(() => Data_OnAfterAdd(sender, args))), true); DataEventSystemFacade.SubscribeToDataAfterUpdate(_interfaceType, - (sender, args) => GetActionContainer().Add(() => Data_OnAfterUpdate(sender, args)), + (sender, args) => CatchAll(() => GetActionContainer().Add(() => Data_OnAfterUpdate(sender, args))), true); DataEventSystemFacade.SubscribeToDataDeleted(_interfaceType, - (sender, args) => GetActionContainer().Add(() => Data_OnDeleted(sender, args)), + (sender, args) => CatchAll(() => GetActionContainer().Add(() => Data_OnDeleted(sender, args))), true); } @@ -65,17 +68,17 @@ private IEnumerable GetCultures(IData data) return DataLocalizationFacade.ActiveLocalizationCultures; } - private bool IgnoreNotification(DataEventArgs args) => !_listeners.Any(); + private bool IgnoreNotifications() => !_listeners.Any(); private void Data_OnAfterAdd(object sender, DataEventArgs dataEventArgs) { - if (IgnoreNotification(dataEventArgs)) return; + if (IgnoreNotifications()) return; try { var data = dataEventArgs.Data; - if (IsPublishedDataFromUnpublishedScope(data)) + if (!DataItemShouldBeIndexed(data)) { return; } @@ -96,12 +99,12 @@ private void Data_OnAfterAdd(object sender, DataEventArgs dataEventArgs) private void Data_OnAfterUpdate(object sender, DataEventArgs dataEventArgs) { - if (IgnoreNotification(dataEventArgs)) return; + if (IgnoreNotifications()) return; try { var data = dataEventArgs.Data; - bool toBeDeleted = IsPublishedDataFromUnpublishedScope(data); + bool toBeDeleted = !DataItemShouldBeIndexed(data); if (toBeDeleted) { @@ -125,7 +128,7 @@ private void Data_OnAfterUpdate(object sender, DataEventArgs dataEventArgs) private void Data_OnDeleted(object sender, DataEventArgs dataEventArgs) { - if (IgnoreNotification(dataEventArgs)) return; + if (IgnoreNotifications()) return; var data = dataEventArgs.Data; DeleteDocuments(data); @@ -154,5 +157,17 @@ private bool IsPublishedDataFromUnpublishedScope(IData data) && data.DataSourceId.PublicationScope == PublicationScope.Unpublished && ((IPublishControlled)data).PublicationStatus == GenericPublishProcessController.Published; } + + private void CatchAll(Action action) + { + try + { + action(); + } + catch (Exception ex) + { + Log.LogError(nameof(DataChangesIndexNotifier), ex); + } + } } } diff --git a/Composite/packages.config b/Composite/packages.config index eb202d024d..47ee7dc78c 100644 --- a/Composite/packages.config +++ b/Composite/packages.config @@ -1,4 +1,4 @@ - + @@ -9,16 +9,12 @@ - - - - diff --git a/Package.nuspec b/Package.nuspec index 9d74c8761c..6054b350dd 100644 --- a/Package.nuspec +++ b/Package.nuspec @@ -12,7 +12,7 @@ true Contains dll-s distributed with C1 CMS. - Copyright 2017 + Copyright 2018 C1CMS OrckestraCMS CompositeC1 cms @@ -22,15 +22,15 @@ - - - - - - - + + + + + + + - - + + diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositecharmap/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositecharmap/plugin.min.js index 22fda69c3a..563452e05b 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositecharmap/plugin.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositecharmap/plugin.min.js @@ -17,9 +17,9 @@ new function () { */ getInfo : function() { return { - longname : "Composite Character Map Plugin", - author : "Composite A/S", - authorurl : "http://www.composite.net", + longname: "Orckestra Character Map Plugin", + author : "Orckestra A/S", + authorurl : "https://c1.orckestra.com/", infourl : null, version : tinymce.majorVersion + "." + tinymce.minorVersion }; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositecomponent/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositecomponent/plugin.min.js index 8f55fbf268..9e80e6f8a3 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositecomponent/plugin.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositecomponent/plugin.min.js @@ -1,4 +1,4 @@ -/** +/** * Composite plugin. */ new function () { @@ -23,8 +23,8 @@ new function () { getInfo: function () { return { longname: "Composite Component Plugin", - author: "Composite A/S", - authorurl: "http://www.composite.net", + author: "Orckestra A/S", + authorurl: "https://c1.orckestra.com/", infourl: null, version: tinymce.majorVersion + "." + tinymce.minorVersion }; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositefield/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositefield/plugin.min.js index eaa1d522df..8e4e81c38a 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositefield/plugin.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositefield/plugin.min.js @@ -16,8 +16,8 @@ new function () { getInfo : function() { return { longname : "Composite Field Plugin", - author : "Composite A/S", - authorurl : "http://www.composite.net", + author : "Orckestra A/S", + authorurl : "https://c1.orckestra.com/", infourl : null, version : tinymce.majorVersion + "." + tinymce.minorVersion }; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositeimage/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositeimage/plugin.min.js index 9b01d21e71..bd2ca783bd 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositeimage/plugin.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositeimage/plugin.min.js @@ -25,8 +25,8 @@ new function () { getInfo: function () { return { longname: "Composite Image Plugin", - author: "Composite A/S", - authorurl: "http://www.composite.net", + author: "Orckestra A/S", + authorurl: "https://c1.orckestra.com/", infourl: null, version: tinymce.majorVersion + "." + tinymce.minorVersion }; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositelink/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositelink/plugin.min.js index 683f58fd43..998dc0626e 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositelink/plugin.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositelink/plugin.min.js @@ -10,8 +10,8 @@ new function () { getInfo : function() { return { longname : "Composite Link", - author : "Composite A/S", - authorurl : "http://www.composite.net", + author : "Orckestra A/S", + authorurl : "https://c1.orckestra.com/", infourl : null, version : tinymce.majorVersion + "." + tinymce.minorVersion }; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositeplugin/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositeplugin/plugin.min.js index a4018c0bb5..8e3b49a604 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositeplugin/plugin.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositeplugin/plugin.min.js @@ -16,8 +16,8 @@ new function () { getInfo : function() { return { longname : "Composite Plugin", - author : "Composite A/S", - authorurl : "http://www.composite.net", + author : "Orckestra A/S", + authorurl : "https://c1.orckestra.com/", infourl : null, version : tinymce.majorVersion + "." + tinymce.minorVersion }; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositerendering/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositerendering/plugin.min.js index 80da63c18b..971ef9a8af 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositerendering/plugin.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositerendering/plugin.min.js @@ -59,8 +59,8 @@ new function () { getInfo: function () { return { longname: "Composite Rendering Plugin", - author: "Composite A/S", - authorurl: "http://www.composite.net", + author: "Orckestra A/S", + authorurl: "https://c1.orckestra.com/", infourl: null, version: tinymce.majorVersion + "." + tinymce.minorVersion }; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositespellcheck/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositespellcheck/plugin.min.js index 1e7bf9437b..1df85e262a 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositespellcheck/plugin.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositespellcheck/plugin.min.js @@ -1,4 +1,4 @@ -/** +/** * Composite plugin. This plugin does nothing, it is simply a template. */ new function () { @@ -16,8 +16,8 @@ new function () { getInfo: function () { return { longname: "Composite SpellCheck", - author: "Composite A/S", - authorurl: "http://www.composite.net", + author: "Orckestra A/S", + authorurl: "https://c1.orckestra.com/", infourl: null, version: tinymce.majorVersion + "." + tinymce.minorVersion }; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositetable/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositetable/plugin.min.js index 77f4cab0ab..37424d8b82 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositetable/plugin.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositetable/plugin.min.js @@ -28,8 +28,8 @@ new function () { getInfo: function () { return { longname: "Composite Table Plugin", - author: "Composite A/S", - authorurl: "http://www.composite.net", + author: "Orckestra A/S", + authorurl: "https://c1.orckestra.com/", infourl: null, version: tinymce.majorVersion + "." + tinymce.minorVersion }; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositetext/plugin.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositetext/plugin.min.js index 50623f8891..99270f97b4 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositetext/plugin.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/plugins/compositetext/plugin.min.js @@ -18,8 +18,8 @@ new function () { getInfo : function() { return { longname : "Composite Text Plugin", - author : "Composite A/S", - authorurl : "http://www.composite.net", + author : "Orckestra A/S", + authorurl : "https://c1.orckestra.com/", infourl : null, version : tinymce.majorVersion + "." + tinymce.minorVersion }; diff --git a/Website/Composite/content/misc/editors/visualeditor/tinymce/themes/composite/theme.min.js b/Website/Composite/content/misc/editors/visualeditor/tinymce/themes/composite/theme.min.js index d577c4b2f5..cfc0222d1a 100644 --- a/Website/Composite/content/misc/editors/visualeditor/tinymce/themes/composite/theme.min.js +++ b/Website/Composite/content/misc/editors/visualeditor/tinymce/themes/composite/theme.min.js @@ -36,8 +36,8 @@ new function () { this.getInfo = function() { return { longname : 'Composite theme', - author : 'Composite A/S', - authorurl : 'http://www.composite.net', + author : 'Orckestra A/S', + authorurl : 'https://c1.orckestra.com/', version : tinymce.majorVersion + "." + tinymce.minorVersion } } diff --git a/Website/Composite/scripts/source/top/core/Application.js b/Website/Composite/scripts/source/top/core/Application.js index 0183c7b396..b1b97f1b21 100644 --- a/Website/Composite/scripts/source/top/core/Application.js +++ b/Website/Composite/scripts/source/top/core/Application.js @@ -387,21 +387,30 @@ _Application.prototype = { }, /** - * Log out. Notice that LogoutService currently returns true no matter what... - * @return {boolean} - */ - logout : function () { - - var result = false; - if ( this.isLoggedIn ) { - this.isLoggedIn = false; - this.isLoggedOut = true; - result = LoginService.Logout ( true ); - if ( !result ) { - alert ( "Logout failed." ); + * Log out. + * @return {Promise} + */ + logout: function () { + var self = this; + var promise = new Promise(function (resolve, reject) { + if (self.isLoggedIn) { + self.isLoggedIn = false; + self.isLoggedOut = true; + LoginService.Logout(true, function (result, fault) { + if (fault) { + self.isLoggedIn = true; + self.isLoggedOut = false; + alert("Logout failed."); + reject(); + } else { + resolve(result); + } + }); + } - } - return result; + }); + + return promise; }, /** @@ -920,10 +929,14 @@ _Application.prototype = { if ( FlowControllerService != null ) { FlowControllerService.ReleaseAllConsoleResources ( Application.CONSOLE_ID ); } - if ( this.logout ()) { - top.close(); - location.reload(); - } + + this.logout().then(function (redirectLocation) { + if (redirectLocation) { + location = redirectLocation; + } else { + location.reload(); + } + }); }, /** diff --git a/Website/Composite/scripts/source/top/services/WebServiceProxy.js b/Website/Composite/scripts/source/top/services/WebServiceProxy.js index afd96a7eec..1de2ca81dd 100644 --- a/Website/Composite/scripts/source/top/services/WebServiceProxy.js +++ b/Website/Composite/scripts/source/top/services/WebServiceProxy.js @@ -84,13 +84,10 @@ WebServiceProxy.createProxyOperation = function (operation) { var self = this; var response = request.asyncInvoke(operation.address, function (response) { self._log(operation, response); - + var soapFault = null; if (response) { if (response.fault) { - result = SOAPFault.newInstance(operation, response.fault); - if (WebServiceProxy.isFaultHandler) { - WebServiceProxy.handleFault(result, request, response); - } + soapFault = SOAPFault.newInstance(operation, response.fault); } else { if (WebServiceProxy.isDOMResult) { result = response.document; @@ -100,7 +97,7 @@ WebServiceProxy.createProxyOperation = function (operation) { } } request.dispose(); - onresponse(result); + onresponse(result, soapFault); }); } else { var request = operation.encoder.encode( diff --git a/Website/Composite/services/Admin/DownloadFile.ashx b/Website/Composite/services/Admin/DownloadFile.ashx index 2f2102a77a..b3c8117065 100644 --- a/Website/Composite/services/Admin/DownloadFile.ashx +++ b/Website/Composite/services/Admin/DownloadFile.ashx @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.Versioning; +using System.Runtime.InteropServices; using System.Web; using Composite; using Composite.Core.IO; @@ -127,13 +129,15 @@ public class DownloadFile : IHttpHandler if (assemblyInfo != null) { + var attributes = assemblyInfo.CustomAttributes; + lines.AddRange(new [] { "--------- Version and metadata ---------", "", "Assembly Name : " + assemblyInfo.GetName().Name, "Version : " + assemblyInfo.GetName().Version, - "Is Debug Dll : " + assemblyInfo.CustomAttributes.Any( + "Is Debug Dll : " + attributes.Any( a => a.AttributeType == typeof(DebuggableAttribute) && a.ConstructorArguments.Any( attr => attr.ArgumentType == typeof(DebuggableAttribute.DebuggingModes) @@ -142,6 +146,18 @@ public class DownloadFile : IHttpHandler "ImageRuntimeVersion : " + assemblyInfo.ImageRuntimeVersion, "Full Name : " + assemblyInfo.FullName }); + + var guidAttribute = attributes.FirstOrDefault(attr => attr.AttributeType == typeof(GuidAttribute)); + if (guidAttribute != null) + { + lines.Add("Guid : " + (string) guidAttribute.ConstructorArguments.First().Value); + } + + var targetFrameworkAttr = attributes.FirstOrDefault(attr => attr.AttributeType == typeof(TargetFrameworkAttribute)); + if (targetFrameworkAttr != null) + { + lines.Add("Target Framework : " + (string) targetFrameworkAttr.ConstructorArguments.First().Value); + } } else { @@ -195,7 +211,7 @@ public class DownloadFile : IHttpHandler - var text = string.Join(Environment.NewLine, lines.Select(line => context.Server.HtmlEncode(line))); + var text = string.Join(Environment.NewLine, lines); context.Response.Write(text); } diff --git a/Website/Composite/services/Login/Login.asmx b/Website/Composite/services/Login/Login.asmx index 13180ab1c5..ac8a4576f7 100644 --- a/Website/Composite/services/Login/Login.asmx +++ b/Website/Composite/services/Login/Login.asmx @@ -1,4 +1,4 @@ -<%@ WebService Language="C#" Class="Composite.Services.Login" %> +<%@ WebService Language="C#" Class="Composite.Services.Login" %> using System; using System.Collections.Generic; @@ -73,10 +73,9 @@ namespace Composite.Services } [WebMethod] - public bool Logout(bool dummy) + public string Logout(bool dummy) { - UserValidationFacade.Logout(); - return true; + return UserValidationFacade.Logout(); } [WebMethod] diff --git a/Website/DebugBuild.Web.config b/Website/DebugBuild.Web.config index faf345f6fa..cc15d41f19 100644 --- a/Website/DebugBuild.Web.config +++ b/Website/DebugBuild.Web.config @@ -1,4 +1,4 @@ - + @@ -29,7 +29,7 @@ - + @@ -87,18 +87,6 @@ - - - - - - - - - - - - diff --git a/Website/ReleaseBuild.Web.config b/Website/ReleaseBuild.Web.config index b3b5930b61..c271a9b782 100644 --- a/Website/ReleaseBuild.Web.config +++ b/Website/ReleaseBuild.Web.config @@ -1,4 +1,4 @@ - + @@ -29,7 +29,7 @@ - + @@ -87,18 +87,6 @@ - - - - - - - - - - - - diff --git a/Website/WebSite.csproj b/Website/WebSite.csproj index c2189c3750..3f8d4aeeda 100644 --- a/Website/WebSite.csproj +++ b/Website/WebSite.csproj @@ -11,7 +11,7 @@ Library Composite Composite.Website - v4.6.1 + v4.7.1 SAK SAK SAK @@ -28,6 +28,7 @@ + true @@ -94,12 +95,6 @@ - - ..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll - - - ..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll - ..\packages\System.Reactive.Core.3.0.0\lib\net46\System.Reactive.Core.dll True @@ -116,12 +111,6 @@ False ..\Packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll - - ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll - - - ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - @@ -2771,7 +2760,6 @@ -