From 3a4a6579d3900fba431d219de8d753181fb01f1b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 30 Aug 2013 10:40:29 +0200 Subject: [PATCH 01/18] Fixes: U4-2711 DAMP throws unknown exception on image upload with image cropper --- src/Umbraco.Core/Models/PropertyExtensions.cs | 2 +- src/umbraco.editorControls/imagecropper/DataTypeData.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Models/PropertyExtensions.cs b/src/Umbraco.Core/Models/PropertyExtensions.cs index ae01532c87..2ef594c8c2 100644 --- a/src/Umbraco.Core/Models/PropertyExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyExtensions.cs @@ -43,7 +43,7 @@ namespace Umbraco.Core.Models //This seems to fail during testing //SD: With the new null checks below, this shouldn't fail anymore. var dt = property.PropertyType.DataType(property.Id, dataTypeService); - if (dt != null && dt.Data != null) + if (dt != null && dt.Data != null && dt.Data.Value != null) { //We've already got the value for the property so we're going to give it to the // data type's data property so it doesn't go re-look up the value from the db again. diff --git a/src/umbraco.editorControls/imagecropper/DataTypeData.cs b/src/umbraco.editorControls/imagecropper/DataTypeData.cs index 7c667aaa24..9c68edb04d 100644 --- a/src/umbraco.editorControls/imagecropper/DataTypeData.cs +++ b/src/umbraco.editorControls/imagecropper/DataTypeData.cs @@ -8,14 +8,14 @@ namespace umbraco.editorControls.imagecropper public override XmlNode ToXMl(XmlDocument data) { - if (Value.ToString() != "") { - XmlDocument xd = new XmlDocument(); + if (Value != null && Value.ToString() != "") + { + var xd = new XmlDocument(); xd.LoadXml(Value.ToString()); return data.ImportNode(xd.DocumentElement, true); - } else { - return base.ToXMl(data); } + return base.ToXMl(data); } } } \ No newline at end of file From d559411187d100243057e53667a5107ea03e467a Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 30 Aug 2013 14:15:51 +1000 Subject: [PATCH 02/18] Fixes U4-2300 6.1.0: Changing a template's parent causes tree to collapse --- .../umbraco/settings/editTemplate.aspx | 2 + .../umbraco_client/Editors/EditTemplate.js | 56 ++++++++++++++----- .../umbraco_client/Editors/EditView.js | 32 +++++++++-- .../WebServices/SaveFileController.cs | 54 ++++++++++++------ 4 files changed, 108 insertions(+), 36 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/settings/editTemplate.aspx b/src/Umbraco.Web.UI/umbraco/settings/editTemplate.aspx index 1308d96298..84a2579687 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/editTemplate.aspx +++ b/src/Umbraco.Web.UI/umbraco/settings/editTemplate.aspx @@ -1,5 +1,6 @@ <%@ Page MasterPageFile="../masterpages/umbracoPage.Master" Language="c#" CodeBehind="EditTemplate.aspx.cs" ValidateRequest="false" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Umbraco.Settings.EditTemplate" %> +<%@ Import Namespace="Umbraco.Core" %> <%@ Import Namespace="Umbraco.Core.IO" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> @@ -19,6 +20,7 @@ jQuery(document).ready(function() { //create the editor editor = new Umbraco.Editors.EditTemplate({ + restServiceLocation: "<%= Url.GetSaveFileServicePath() %>", umbracoPath: '<%= IOHelper.ResolveUrl(SystemDirectories.Umbraco) %>', editorClientId: '<%= editorSource.ClientID %>', useMasterPages: <%=umbraco.UmbracoSettings.UseAspNetMasterPages.ToString().ToLower()%>, diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditTemplate.js b/src/Umbraco.Web.UI/umbraco_client/Editors/EditTemplate.js index c3d142e314..adcf8de50c 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditTemplate.js +++ b/src/Umbraco.Web.UI/umbraco_client/Editors/EditTemplate.js @@ -101,27 +101,57 @@ save: function(templateName, templateAlias, codeVal) { var self = this; - umbraco.presentation.webservices.codeEditorSave.SaveTemplate( - templateName, templateAlias, codeVal, self._opts.templateId, this._opts.masterPageDropDown.val(), - function(t) { self.submitSucces(t); }, - function(t) { self.submitFailure(t); }); - + $.post(self._opts.restServiceLocation + "SaveTemplate", + JSON.stringify({ + templateName: templateName, + templateAlias: templateAlias, + templateContents: codeVal, + templateId: self._opts.templateId, + masterTemplateId: this._opts.masterPageDropDown.val() + }), + function (e) { + if (e.success) { + self.submitSuccess(e); + } else { + self.submitFailure(e.message, e.header); + } + }); + }, - submitSucces: function(t) { - if (t != 'true') { - top.UmbSpeechBubble.ShowMessage('error', this._opts.text.templateErrorHeader, this._opts.text.templateErrorText); + submitSuccess: function (args) { + var msg = args.message; + var header = args.header; + var path = this._opts.treeSyncPath; + var pathChanged = false; + if (args.path) { + if (path != args.path) { + pathChanged = true; + } + path = args.path; + } + + top.UmbSpeechBubble.ShowMessage('save', header, msg); + UmbClientMgr.mainTree().setActiveTreeType('templates'); + if (pathChanged) { + UmbClientMgr.mainTree().moveNode(this._opts.templateId, path); } else { - top.UmbSpeechBubble.ShowMessage('save', this._opts.text.templateSavedHeader, this._opts.text.templateSavedText); + UmbClientMgr.mainTree().syncTree(path, true); } - UmbClientMgr.mainTree().setActiveTreeType('templates'); - UmbClientMgr.mainTree().syncTree(this._opts.treeSyncPath, true); }, - submitFailure: function(t) { - top.UmbSpeechBubble.ShowMessage('error', this._opts.text.templateErrorHeader, this._opts.text.templateErrorText); + submitFailure: function (err, header) { + top.UmbSpeechBubble.ShowMessage('error', header, err); } }); + + //Set defaults for jQuery ajax calls. + $.ajaxSetup({ + dataType: 'json', + cache: false, + contentType: 'application/json; charset=utf-8' + }); + })(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js b/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js index 646890a042..655a7b1609 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js +++ b/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js @@ -80,7 +80,7 @@ }), function(e) { if (e.success) { - self.submitSuccess(e.message, e.header); + self.submitSuccess(e); } else { self.submitFailure(e.message, e.header); } @@ -97,7 +97,7 @@ }), function(e) { if (e.success) { - self.submitSuccess(e.message, e.header); + self.submitSuccess(e); } else { self.submitFailure(e.message, e.header); } @@ -105,8 +105,20 @@ } }, - submitSuccess: function (err, header) { - top.UmbSpeechBubble.ShowMessage('save', header, err); + submitSuccess: function (args) { + + var msg = args.message; + var header = args.header; + var path = this._opts.treeSyncPath; + var pathChanged = false; + if (args.path) { + if (path != args.path) { + pathChanged = true; + } + path = args.path; + } + + top.UmbSpeechBubble.ShowMessage('save', header, msg); UmbClientMgr.mainTree().setActiveTreeType(this._opts.currentTreeType); @@ -114,11 +126,19 @@ if (this._opts.editorType == "Template") { //templates are different because they are ID based, whereas view files are file based without a static id - UmbClientMgr.mainTree().syncTree(this._opts.treeSyncPath, true); + + if (pathChanged) { + UmbClientMgr.mainTree().moveNode(this._opts.templateId, path); + } + else { + UmbClientMgr.mainTree().syncTree(path, true); + } + + } else { //we need to pass in the newId parameter so it knows which node to resync after retreival from the server - UmbClientMgr.mainTree().syncTree(this._opts.treeSyncPath, true, null, newFilePath.split("/")[1]); + UmbClientMgr.mainTree().syncTree(path, true, null, newFilePath.split("/")[1]); } //then we need to update our current tree sync path to represent the new one diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 39c7baa4e6..7b90da05c1 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -8,7 +8,9 @@ using Umbraco.Web.Macros; using Umbraco.Web.Mvc; using umbraco; using umbraco.cms.businesslogic.macro; +using System.Collections.Generic; using Umbraco.Core; + using Template = umbraco.cms.businesslogic.template.Template; namespace Umbraco.Web.WebServices @@ -90,15 +92,22 @@ namespace Umbraco.Web.WebServices public JsonResult SaveTemplate(string templateName, string templateAlias, string templateContents, int templateId, int masterTemplateId) { Template t; + bool pathChanged = false; try { t = new Template(templateId) { Text = templateName, - Alias = templateAlias, - MasterTemplate = masterTemplateId, + Alias = templateAlias, Design = templateContents }; + + //check if the master page has changed + if (t.MasterTemplate != masterTemplateId) + { + pathChanged = true; + t.MasterTemplate = masterTemplateId; + } } catch (ArgumentException ex) { @@ -110,7 +119,17 @@ namespace Umbraco.Web.WebServices { t.Save(); - return Success(ui.Text("speechBubbles", "templateSavedText"), ui.Text("speechBubbles", "templateSavedHeader")); + //ensure the correct path is synced as the parent might have been changed + // http://issues.umbraco.org/issue/U4-2300 + if (pathChanged) + { + //need to re-look it up + t = new Template(templateId); + } + var syncPath = "-1,init," + t.Path.Replace("-1,", ""); + + return Success(ui.Text("speechBubbles", "templateSavedText"), ui.Text("speechBubbles", "templateSavedHeader"), + new {path = syncPath}); } catch (Exception ex) { @@ -118,20 +137,21 @@ namespace Umbraco.Web.WebServices } } - /// - /// Returns a successful message - /// - /// The message to display in the speach bubble - /// The header to display in the speach bubble - /// - private JsonResult Success(string message, string header) - { - return Json(new - { - success = true, - message = message, - header = header - }); + /// + /// Returns a successful message + /// + /// The message to display in the speach bubble + /// The header to display in the speach bubble + /// + /// + private JsonResult Success(string message, string header, object additionalVals = null) + { + var d = additionalVals == null ? new Dictionary() : additionalVals.ToDictionary(); + d["success"] = true; + d["message"] = message; + d["header"] = header; + + return Json(d); } /// From 2b408df24cf31b918025fa0ef2df9c8b388cf3c5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 29 Aug 2013 13:33:01 +1000 Subject: [PATCH 03/18] Fixes: U4-2727 PluginManager's TypeList can contain duplicates - updates the locks to be upgradeable read locks, changes over all lists/collecitons to be HashSet this ensures no duplicates and also improves performance. --- src/Umbraco.Core/TypeFinder.cs | 709 +++++++++++++++++---------------- 1 file changed, 367 insertions(+), 342 deletions(-) diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs index 3cbe21991b..36a5deff75 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/TypeFinder.cs @@ -19,54 +19,56 @@ using Umbraco.Core.IO; namespace Umbraco.Core { - /// - /// A utility class to find all classes of a certain type by reflection in the current bin folder - /// of the web application. - /// - public static class TypeFinder - { - - private static readonly ConcurrentBag LocalFilteredAssemblyCache = new ConcurrentBag(); - private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim(); - private static ReadOnlyCollection _allAssemblies = null; - private static ReadOnlyCollection _binFolderAssemblies = null; - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + /// + /// A utility class to find all classes of a certain type by reflection in the current bin folder + /// of the web application. + /// + public static class TypeFinder + { + private static readonly HashSet LocalFilteredAssemblyCache = new HashSet(); + private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim(); + private static HashSet _allAssemblies = null; + private static HashSet _binFolderAssemblies = null; + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - /// - /// lazily load a reference to all assemblies and only local assemblies. - /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder - /// - /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been - /// loaded in the CLR, not all assemblies. - /// See these threads: - /// http://issues.umbraco.org/issue/U5-198 - /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app - /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl - /// - internal static IEnumerable GetAllAssemblies() - { - if (_allAssemblies == null) - { - using (new WriteLock(Locker)) - { - List assemblies = null; - try - { - var isHosted = HttpContext.Current != null; + /// + /// lazily load a reference to all assemblies and only local assemblies. + /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder + /// + /// + /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been + /// loaded in the CLR, not all assemblies. + /// See these threads: + /// http://issues.umbraco.org/issue/U5-198 + /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app + /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl + /// + internal static HashSet GetAllAssemblies() + { + using (var lck = new UpgradeableReadLock(Locker)) + { + if (_allAssemblies == null) + { - try - { - if (isHosted) - { - assemblies = new List(BuildManager.GetReferencedAssemblies().Cast()); - } - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; - } + lck.UpgradeToWriteLock(); + + HashSet assemblies = null; + try + { + var isHosted = HttpContext.Current != null; + + try + { + if (isHosted) + { + assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + } + } + catch (InvalidOperationException e) + { + if (!(e.InnerException is SecurityException)) + throw; + } if (assemblies == null) @@ -77,7 +79,7 @@ namespace Umbraco.Core var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new List(); + assemblies = new HashSet(); foreach (var a in binAssemblyFiles) { try @@ -99,151 +101,161 @@ namespace Umbraco.Core } } } - + //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. - if (!assemblies.Any()) - { - assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().ToList()); - } + if (!assemblies.Any()) + { + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + assemblies.Add(a); + } + } - //here we are trying to get the App_Code assembly - var fileExtensions = new[] {".cs", ".vb"}; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); - //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))) - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } + //here we are trying to get the App_Code assembly + var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported + var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); + //check if the folder exists and if there are any files in it with the supported file extensions + if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))) + { + var appCodeAssembly = Assembly.Load("App_Code"); + if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already + assemblies.Add(appCodeAssembly); + } - //now set the _allAssemblies - _allAssemblies = new ReadOnlyCollection(assemblies); + //now set the _allAssemblies + _allAssemblies = new HashSet(assemblies); - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; + } + catch (InvalidOperationException e) + { + if (!(e.InnerException is SecurityException)) + throw; - _binFolderAssemblies = _allAssemblies; - } - } - } + _binFolderAssemblies = _allAssemblies; + } + } - return _allAssemblies; - } - - /// - /// Returns only assemblies found in the bin folder that have been loaded into the app domain. - /// - /// - /// - /// This will be used if we implement App_Plugins from Umbraco v5 but currently it is not used. - /// - internal static IEnumerable GetBinAssemblies() - { + return _allAssemblies; + } + } - if (_binFolderAssemblies == null) - { - using (new WriteLock(Locker)) - { - var assemblies = GetAssembliesWithKnownExclusions().ToArray(); - var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - var domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName); - var safeDomainAssemblies = new List(); - var binFolderAssemblies = new List(); + /// + /// Returns only assemblies found in the bin folder that have been loaded into the app domain. + /// + /// + /// + /// This will be used if we implement App_Plugins from Umbraco v5 but currently it is not used. + /// + internal static HashSet GetBinAssemblies() + { - foreach (var a in assemblies) - { - try - { - //do a test to see if its queryable in med trust - var assemblyFile = a.GetAssemblyFile(); - safeDomainAssemblies.Add(a); - } - catch (SecurityException) - { - //we will just ignore this because this will fail - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to - //an assembly that is ok. - } - } + if (_binFolderAssemblies == null) + { + using (new WriteLock(Locker)) + { + var assemblies = GetAssembliesWithKnownExclusions().ToArray(); + var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; + var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + var domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName); + var safeDomainAssemblies = new HashSet(); + var binFolderAssemblies = new HashSet(); - foreach (var assemblyName in domainAssemblyNames) - { - try - { - var foundAssembly = - safeDomainAssemblies.FirstOrDefault(a => a.GetAssemblyFile() == assemblyName.GetAssemblyFile()); - if (foundAssembly != null) - { - binFolderAssemblies.Add(foundAssembly); - } - } - catch (SecurityException) - { - //we will just ignore this because if we are trying to do a call to: - // AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName))) - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to - //an assembly that is ok. - } - } + foreach (var a in assemblies) + { + try + { + //do a test to see if its queryable in med trust + var assemblyFile = a.GetAssemblyFile(); + safeDomainAssemblies.Add(a); + } + catch (SecurityException) + { + //we will just ignore this because this will fail + //in medium trust for system assemblies, we get an exception but we just want to continue until we get to + //an assembly that is ok. + } + } - _binFolderAssemblies = new ReadOnlyCollection(binFolderAssemblies); - } - } - return _binFolderAssemblies; - } + foreach (var assemblyName in domainAssemblyNames) + { + try + { + var foundAssembly = + safeDomainAssemblies.FirstOrDefault(a => a.GetAssemblyFile() == assemblyName.GetAssemblyFile()); + if (foundAssembly != null) + { + binFolderAssemblies.Add(foundAssembly); + } + } + catch (SecurityException) + { + //we will just ignore this because if we are trying to do a call to: + // AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName))) + //in medium trust for system assemblies, we get an exception but we just want to continue until we get to + //an assembly that is ok. + } + } - /// - /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan - /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are - /// cached for perforance reasons. - /// - /// - /// - internal static IEnumerable GetAssembliesWithKnownExclusions( - IEnumerable excludeFromResults = null) - { - if (LocalFilteredAssemblyCache.Any()) return LocalFilteredAssemblyCache; - using (new WriteLock(LocalFilteredAssemblyCacheLocker)) - { - var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); - assemblies.ForEach(LocalFilteredAssemblyCache.Add); - } - return LocalFilteredAssemblyCache; - } + _binFolderAssemblies = new HashSet(binFolderAssemblies); + } + } + return _binFolderAssemblies; + } - /// - /// Return a list of found local Assemblies and exluding the ones passed in and excluding the exclusion list filter - /// - /// - /// - /// - private static IEnumerable GetFilteredAssemblies( - IEnumerable excludeFromResults = null, - string[] exclusionFilter = null) - { - if (excludeFromResults == null) - excludeFromResults = new List(); - if (exclusionFilter == null) - exclusionFilter = new string[] { }; + /// + /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan + /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are + /// cached for perforance reasons. + /// + /// + /// + internal static HashSet GetAssembliesWithKnownExclusions( + IEnumerable excludeFromResults = null) + { + using (var lck = new UpgradeableReadLock(LocalFilteredAssemblyCacheLocker)) + { + if (LocalFilteredAssemblyCache.Any()) return LocalFilteredAssemblyCache; - return GetAllAssemblies() - .Where(x => !excludeFromResults.Contains(x) - && !x.GlobalAssemblyCache - && !exclusionFilter.Any(f => x.FullName.StartsWith(f))); - } + lck.UpgradeToWriteLock(); - /// - /// this is our assembly filter to filter out known types that def dont contain types we'd like to find or plugins - /// - /// - /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match - /// - internal static readonly string[] KnownAssemblyExclusionFilter = new[] + var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); + foreach (var a in assemblies) + { + LocalFilteredAssemblyCache.Add(a); + } + + return LocalFilteredAssemblyCache; + } + } + + /// + /// Return a distinct list of found local Assemblies and exluding the ones passed in and excluding the exclusion list filter + /// + /// + /// + /// + private static IEnumerable GetFilteredAssemblies( + IEnumerable excludeFromResults = null, + string[] exclusionFilter = null) + { + if (excludeFromResults == null) + excludeFromResults = new HashSet(); + if (exclusionFilter == null) + exclusionFilter = new string[] { }; + + return GetAllAssemblies() + .Where(x => !excludeFromResults.Contains(x) + && !x.GlobalAssemblyCache + && !exclusionFilter.Any(f => x.FullName.StartsWith(f))); + } + + /// + /// this is our assembly filter to filter out known types that def dont contain types we'd like to find or plugins + /// + /// + /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match + /// + internal static readonly string[] KnownAssemblyExclusionFilter = new[] { "mscorlib,", "System.", @@ -287,11 +299,11 @@ namespace Umbraco.Core /// /// /// - public static IEnumerable FindClassesOfTypeWithAttribute() - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); - } + public static IEnumerable FindClassesOfTypeWithAttribute() + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); + } /// /// Finds any classes derived from the type T that contain the attribute TAttribute @@ -300,11 +312,11 @@ namespace Umbraco.Core /// /// /// - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(assemblies, true); - } + public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(assemblies, true); + } /// /// Finds any classes derived from the type T that contain the attribute TAttribute @@ -314,13 +326,13 @@ namespace Umbraco.Core /// /// /// - public static IEnumerable FindClassesOfTypeWithAttribute( - IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(typeof (T), assemblies, onlyConcreteClasses); - } + public static IEnumerable FindClassesOfTypeWithAttribute( + IEnumerable assemblies, + bool onlyConcreteClasses) + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(typeof(T), assemblies, onlyConcreteClasses); + } /// /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute @@ -330,85 +342,85 @@ namespace Umbraco.Core /// /// /// - public static IEnumerable FindClassesOfTypeWithAttribute( - Type assignTypeFrom, - IEnumerable assemblies, + public static IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + IEnumerable assemblies, bool onlyConcreteClasses) where TAttribute : Attribute - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - return GetClasses(assignTypeFrom, assemblies, onlyConcreteClasses, + { + if (assemblies == null) throw new ArgumentNullException("assemblies"); + + return GetClasses(assignTypeFrom, assemblies, onlyConcreteClasses, //the additional filter will ensure that any found types also have the attribute applied. - t => t.GetCustomAttributes(false).Any()); - } + t => t.GetCustomAttributes(false).Any()); + } - /// - /// Searches all filtered local assemblies specified for classes of the type passed in. - /// - /// - /// - public static IEnumerable FindClassesOfType() - { - return FindClassesOfType(GetAssembliesWithKnownExclusions(), true); - } + /// + /// Searches all filtered local assemblies specified for classes of the type passed in. + /// + /// + /// + public static IEnumerable FindClassesOfType() + { + return FindClassesOfType(GetAssembliesWithKnownExclusions(), true); + } - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses) + { + if (assemblies == null) throw new ArgumentNullException("assemblies"); return GetClasses(typeof(T), assemblies, onlyConcreteClasses); - } + } - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies) - { - return FindClassesOfType(assemblies, true); - } + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + public static IEnumerable FindClassesOfType(IEnumerable assemblies) + { + return FindClassesOfType(assemblies, true); + } - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) - where T : Attribute - { - return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); - } + /// + /// Finds the classes with attribute. + /// + /// + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) + where T : Attribute + { + return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); + } - /// - /// Finds any classes with the attribute. - /// - /// The attribute type - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute( - Type attributeType, - IEnumerable assemblies, - bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); + /// + /// Finds any classes with the attribute. + /// + /// The attribute type + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute( + Type attributeType, + IEnumerable assemblies, + bool onlyConcreteClasses) + { + if (assemblies == null) throw new ArgumentNullException("assemblies"); - if (TypeHelper.IsTypeAssignableFrom(attributeType) == false) - throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type"); + if (TypeHelper.IsTypeAssignableFrom(attributeType) == false) + throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type"); - var foundAssignableTypes = new List(); + var foundAssignableTypes = new HashSet(); var assemblyList = assemblies.ToArray(); @@ -418,71 +430,74 @@ namespace Umbraco.Core var referencedAssemblies = TypeHelper.GetReferencedAssemblies(attributeType, assemblyList); foreach (var a in referencedAssemblies) - { + { //get all types in the assembly that are sub types of the current type var allTypes = GetTypesWithFormattedException(a).ToArray(); - - var types = allTypes.Where(t => TypeHelper.IsNonStaticClass(t) - && (onlyConcreteClasses == false || t.IsAbstract == false) - //the type must have this attribute + + var types = allTypes.Where(t => TypeHelper.IsNonStaticClass(t) + && (onlyConcreteClasses == false || t.IsAbstract == false) + //the type must have this attribute && t.GetCustomAttributes(attributeType, false).Any()); - foundAssignableTypes.AddRange(types); - } + foreach (var t in types) + { + foundAssignableTypes.Add(t); + } + } - return foundAssignableTypes; - } + return foundAssignableTypes; + } - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies) - where T : Attribute - { - return FindClassesWithAttribute(assemblies, true); - } + /// + /// Finds the classes with attribute. + /// + /// + /// The assemblies. + /// + public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies) + where T : Attribute + { + return FindClassesWithAttribute(assemblies, true); + } - /// - /// Finds the classes with attribute in filtered local assemblies - /// - /// - /// - public static IEnumerable FindClassesWithAttribute() - where T : Attribute - { - return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); - } + /// + /// Finds the classes with attribute in filtered local assemblies + /// + /// + /// + public static IEnumerable FindClassesWithAttribute() + where T : Attribute + { + return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); + } - #region Private methods - - /// - /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly - /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type - /// deriving from the base type. - /// - /// - /// - /// - /// An additional filter to apply for what types will actually be included in the return value - /// - private static IEnumerable GetClasses( - Type assignTypeFrom, - IEnumerable assemblies, + #region Private methods + + /// + /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly + /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type + /// deriving from the base type. + /// + /// + /// + /// + /// An additional filter to apply for what types will actually be included in the return value + /// + private static IEnumerable GetClasses( + Type assignTypeFrom, + IEnumerable assemblies, bool onlyConcreteClasses, Func additionalFilter = null) - { + { //the default filter will always return true. if (additionalFilter == null) { additionalFilter = type => true; } - var foundAssignableTypes = new List(); + var foundAssignableTypes = new HashSet(); var assemblyList = assemblies.ToArray(); @@ -510,8 +525,11 @@ namespace Umbraco.Core .ToArray(); //add the types to our list to return - foundAssignableTypes.AddRange(filteredTypes); - + foreach (var t in filteredTypes) + { + foundAssignableTypes.Add(t); + } + //now we need to include types that may be inheriting from sub classes of the type being searched for //so we will search in assemblies that reference those types too. foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly)) @@ -529,54 +547,61 @@ namespace Umbraco.Core //if there's a base class amongst the types then we'll only search for that type. //otherwise we'll have to search for all of them. - var subTypesToSearch = new List(); + var subTypesToSearch = new HashSet(); if (baseClassAttempt.Success) { subTypesToSearch.Add(baseClassAttempt.Result); } else { - subTypesToSearch.AddRange(subTypeList); + foreach (var t in subTypeList) + { + subTypesToSearch.Add(t); + } } - + foreach (var typeToSearch in subTypesToSearch) { //recursively find the types inheriting from this sub type in the other non-scanned assemblies. var foundTypes = GetClasses(typeToSearch, otherAssemblies, onlyConcreteClasses, additionalFilter); - foundAssignableTypes.AddRange(foundTypes); + + foreach (var f in foundTypes) + { + foundAssignableTypes.Add(f); + } } - + } - } - return foundAssignableTypes; - } + } + return foundAssignableTypes; + } - private static IEnumerable GetTypesWithFormattedException(Assembly a) - { - //if the assembly is dynamic, do not try to scan it - if (a.IsDynamic) - return Enumerable.Empty(); + private static IEnumerable GetTypesWithFormattedException(Assembly a) + { + //if the assembly is dynamic, do not try to scan it + if (a.IsDynamic) + return Enumerable.Empty(); - try - { - return a.GetExportedTypes(); - } - catch (ReflectionTypeLoadException ex) - { - var sb = new StringBuilder(); - sb.AppendLine("Could not load types from assembly " + a.FullName + ", errors:"); - foreach (var loaderException in ex.LoaderExceptions.WhereNotNull()) - { - sb.AppendLine("Exception: " + loaderException); - } - throw new ReflectionTypeLoadException(ex.Types, ex.LoaderExceptions, sb.ToString()); - } - } + try + { + return a.GetExportedTypes(); + } + catch (ReflectionTypeLoadException ex) + { + var sb = new StringBuilder(); + sb.AppendLine("Could not load types from assembly " + a.FullName + ", errors:"); + foreach (var loaderException in ex.LoaderExceptions.WhereNotNull()) + { + sb.AppendLine("Exception: " + loaderException); + } + throw new ReflectionTypeLoadException(ex.Types, ex.LoaderExceptions, sb.ToString()); + } + } - #endregion + #endregion - - } + + } } From 52540165f1181cda7bd3d4317e55dcd874825111 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 30 Aug 2013 11:47:39 +0200 Subject: [PATCH 04/18] Creating a partial view now inherits from UmbracoTemplatePage (in 6.2.0 there will be 2 snippets, one for UmbracoViewPage, for MVC experts and one for UmbracoTemplatePage, the default). --- .../umbraco.presentation/umbraco/create/PartialViewTasks.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs index 34552fa879..49648d3291 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasks.cs @@ -94,8 +94,7 @@ namespace umbraco { //write out the template header sw.Write("@inherits "); - sw.Write(typeof(UmbracoViewPage<>).FullName.TrimEnd("`1")); - sw.Write(""); + sw.Write(typeof(UmbracoTemplatePage).FullName.TrimEnd("`1")); } public bool Delete() From 2552dffdadf899c0e9006386e063976693915f1b Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 27 Aug 2013 17:58:32 +0200 Subject: [PATCH 05/18] U4-2691 - fix issue with alt template and internal redirects --- .../Routing/PublishedContentRequest.cs | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index 4eae420fed..6876f01606 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs @@ -140,12 +140,35 @@ namespace Umbraco.Web.Routing // unless a template has been set already by the finder, // template should be null at that point. - var initial = IsInitialPublishedContent; + + // IsInternalRedirect if IsInitial, or already IsInternalRedirect + var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; + + // redirecting to self + if (content.Id == PublishedContent.Id) // neither can be null + { + // no need to set PublishedContent, we're done + IsInternalRedirectPublishedContent = isInternalRedirect; + return; + } + + // else + + // save var template = _template; + var renderingEngine = RenderingEngine; + + // set published content - this resets the template, and sets IsInternalRedirect to false PublishedContent = content; - IsInternalRedirectPublishedContent = (initial && !IsInitialPublishedContent); - if (IsInternalRedirectPublishedContent && UmbracoSettings.For().InternalRedirectPreservesTemplate) + IsInternalRedirectPublishedContent = isInternalRedirect; + + // must restore the template if it's an internal redirect & the config option is set + if (isInternalRedirect && UmbracoSettings.For().InternalRedirectPreservesTemplate) + { + // restore _template = template; + RenderingEngine = renderingEngine; + } } /// @@ -179,9 +202,11 @@ namespace Umbraco.Web.Routing } /// - /// Gets or sets a value indicating whether the current published has been obtained from the - /// initial published content following internal redirections exclusively. + /// Gets or sets a value indicating whether the current published content has been obtained + /// from the initial published content following internal redirections exclusively. /// + /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to + /// apply the internal redirect or not, when content is not the initial content. public bool IsInternalRedirectPublishedContent { get; private set; } /// From fbfdd8d398816d0b2190ae0b172f106110070c55 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 30 Aug 2013 12:00:33 +0200 Subject: [PATCH 06/18] Bump version number --- build/Build.bat | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/Build.bat b/build/Build.bat index d1335ea93e..2664474af3 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -1,5 +1,5 @@ @ECHO OFF -SET release=6.1.4 +SET release=6.1.5 SET comment= SET version=%release% diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 9863edfee3..357bc475c9 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("6.1.4"); + private static readonly Version Version = new Version("6.1.5"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index c4c0ef70f4..8ed914eeeb 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2602,9 +2602,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 6140 + 6150 / - http://localhost:6140 + http://localhost:6150 False False From 9e2733ce69fadeebbb064e828346dae2583680c9 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 26 Aug 2013 15:47:48 +0200 Subject: [PATCH 07/18] U4-2549 - fix issue with last chance content finder Conflicts: src/Umbraco.Web/Umbraco.Web.csproj Conflicts: src/Umbraco.Web/Umbraco.Web.csproj --- .../config/404handlers.Release.config | 2 +- src/Umbraco.Web.UI/config/404handlers.config | 2 +- src/Umbraco.Web/Routing/AliasUrlProvider.cs | 21 ++- .../ContentFinderByNotFoundHandlers.cs | 132 +++++++----------- ...ntentLastChanceFinderByNotFoundHandlers.cs | 101 ++++++++++++++ .../Routing/NotFoundHandlerHelper.cs | 101 ++++++++++++-- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/WebBootManager.cs | 38 +++-- 8 files changed, 279 insertions(+), 119 deletions(-) create mode 100644 src/Umbraco.Web/Routing/ContentLastChanceFinderByNotFoundHandlers.cs diff --git a/src/Umbraco.Web.UI/config/404handlers.Release.config b/src/Umbraco.Web.UI/config/404handlers.Release.config index 11bdbaf8ef..770f7ca64b 100644 --- a/src/Umbraco.Web.UI/config/404handlers.Release.config +++ b/src/Umbraco.Web.UI/config/404handlers.Release.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/404handlers.config b/src/Umbraco.Web.UI/config/404handlers.config index 69bf6f1dd0..b6dd88fa8b 100644 --- a/src/Umbraco.Web.UI/config/404handlers.config +++ b/src/Umbraco.Web.UI/config/404handlers.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index f8e270a16c..d8846afd60 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -88,14 +88,25 @@ namespace Umbraco.Web.Routing #region Utilities - private bool FindByUrlAliasEnabled + private static bool FindByUrlAliasEnabled { get { - var hasFinder = ContentFinderResolver.Current.ContainsType(); - var hasHandler = ContentFinderResolver.Current.ContainsType() - && NotFoundHandlerHelper.CustomHandlerTypes.Contains(typeof(global::umbraco.SearchForAlias)); - return hasFinder || hasHandler; + // finder + if (ContentFinderResolver.Current.ContainsType()) + return true; + + // handler wrapped into a finder + if (ContentFinderResolver.Current.ContainsType>()) + return true; + + // handler wrapped into special finder + if (ContentFinderResolver.Current.ContainsType() + && NotFoundHandlerHelper.IsNotFoundHandlerEnabled()) + return true; + + // anything else, we can't detect + return false; } } diff --git a/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs b/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs index 6c10a45dd7..cb80429167 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs @@ -29,115 +29,77 @@ namespace Umbraco.Web.Routing #region Copied over and adapted from presentation.requestHandler - void HandlePageNotFound(PublishedContentRequest docRequest) + private static void HandlePageNotFound(PublishedContentRequest docRequest) { var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers(); LogHelper.Debug("Running for legacy url='{0}'.", () => url); - foreach (var handler in GetNotFoundHandlers()) + foreach (var handler in NotFoundHandlerHelper.GetNotFoundHandlers()) { - IContentFinder finder = null; var handlerName = handler.GetType().FullName; - LogHelper.Debug("Handler '{0}'.", () => handlerName); - // replace with our own implementation - if (handler is global::umbraco.SearchForAlias) - finder = new ContentFinderByUrlAlias(); - else if (handler is global::umbraco.SearchForProfile) - finder = new ContentFinderByProfile(); - else if (handler is global::umbraco.SearchForTemplate) - finder = new ContentFinderByNiceUrlAndTemplate(); - else if (handler is global::umbraco.handle404) - finder = new ContentFinderByLegacy404(); - + var finder = NotFoundHandlerHelper.SubsituteFinder(handler); if (finder != null) { var finderName = finder.GetType().FullName; LogHelper.Debug("Replace handler '{0}' by new finder '{1}'.", () => handlerName, () => finderName); - if (finder.TryFindContent(docRequest)) - { - // do NOT set docRequest.PublishedContent again here as - // it would clear any template that the finder might have set - LogHelper.Debug("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id); - if (docRequest.Is404) - LogHelper.Debug("Finder '{0}' set status to 404.", () => finderName); - // if we found a document, break, don't look at more handler -- we're done - break; - } + // can't find a document => continue with other handlers + if (finder.TryFindContent(docRequest) == false) + continue; - // if we did not find a document, continue, look at other handlers - continue; - } + // found a document => break, don't run other handlers, we're done - // else it's a legacy handler, run + // in theory an IContentFinder can return true yet set no document + // but none of the substitued finders (see SubstituteFinder) do it. - if (handler.Execute(url) && handler.redirectID > 0) - { - var redirectId = handler.redirectID; - docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId); + // do NOT set docRequest.PublishedContent again here + // as it would clear any template that the finder might have set - if (!docRequest.HasPublishedContent) - { - LogHelper.Debug("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId); - break; - } + LogHelper.Debug("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id); + if (docRequest.Is404) + LogHelper.Debug("Finder '{0}' set status to 404.", () => finderName); - LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId); - - if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404) - { - LogHelper.Debug("Handler '{0}' set status code to 404.", () => handlerName); - docRequest.Is404 = true; - } - - //// check for caching - //if (handler.CacheUrl) - //{ - // if (url.StartsWith("/")) - // url = "/" + url; - - // var cacheKey = (currentDomain == null ? "" : currentDomain.Name) + url; - // var culture = currentDomain == null ? null : currentDomain.Language.CultureAlias; - // SetCache(cacheKey, new CacheEntry(handler.redirectID.ToString(), culture)); - - // HttpContext.Current.Trace.Write("NotFoundHandler", - // string.Format("Added to cache '{0}', {1}.", url, handler.redirectID)); - //} - - // if we found a document, break, don't look at more handler -- we're done + LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => docRequest.PublishedContent.Id); break; } - // if we did not find a document, continue, look at other handlers + // else it's a legacy handler: run + + // can't find a document => continue with other handlers + if (handler.Execute(url) == false || handler.redirectID <= 0) + continue; + + // found a document ID => ensure it's a valid document + var redirectId = handler.redirectID; + docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId); + + if (docRequest.HasPublishedContent == false) + { + // the handler said it could handle the url, and returned a content ID + // yet that content ID is invalid... should we run the other handlers? + // I don't think so, not here, let the "last chance" finder take care. + // so, break. + + LogHelper.Debug("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId); + break; + } + + // found a valid document => break, don't run other handlers, we're done + + LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId); + + if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404) + { + LogHelper.Debug("Handler '{0}' set status code to 404.", () => handlerName); + docRequest.Is404 = true; + } + + break; } } - IEnumerable GetNotFoundHandlers() - { - // instanciate new handlers - // using definition cache - - var handlers = new List(); - - foreach (var type in NotFoundHandlerHelper.CustomHandlerTypes) - { - try - { - var handler = Activator.CreateInstance(type) as INotFoundHandler; - if (handler != null) - handlers.Add(handler); - } - catch (Exception e) - { - LogHelper.Error(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e); - } - } - - return handlers; - } - #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/ContentLastChanceFinderByNotFoundHandlers.cs b/src/Umbraco.Web/Routing/ContentLastChanceFinderByNotFoundHandlers.cs new file mode 100644 index 0000000000..115e8cabc0 --- /dev/null +++ b/src/Umbraco.Web/Routing/ContentLastChanceFinderByNotFoundHandlers.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using umbraco.interfaces; + +namespace Umbraco.Web.Routing +{ + /// + /// Provides an implementation of that runs legacy INotFoundHandler in "last chance" situation. + /// + public class ContentLastChanceFinderByNotFoundHandlers : IContentFinder + { + // notes + // + // at the moment we load the legacy INotFoundHandler + // excluding those that have been replaced by proper finders, + // and run them. + + /// + /// Tries to find and assign an Umbraco document to a PublishedContentRequest. + /// + /// The PublishedContentRequest. + /// A value indicating whether an Umbraco document was found and assigned. + public bool TryFindContent(PublishedContentRequest docRequest) + { + HandlePageNotFound(docRequest); + return docRequest.HasPublishedContent; + } + + #region Copied over and adapted from presentation.requestHandler + + private static void HandlePageNotFound(PublishedContentRequest docRequest) + { + var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers(); + LogHelper.Debug("Running for legacy url='{0}'.", () => url); + + var handler = NotFoundHandlerHelper.GetNotFoundLastChanceHandler(); + var handlerName = handler.GetType().FullName; + LogHelper.Debug("Handler '{0}'.", () => handlerName); + + var finder = NotFoundHandlerHelper.SubsituteFinder(handler); + if (finder != null) + { + var finderName = finder.GetType().FullName; + LogHelper.Debug("Replace handler '{0}' by new finder '{1}'.", () => handlerName, () => finderName); + + // can't find a document => exit + if (finder.TryFindContent(docRequest) == false) + return; + + // found a document => we're done + + // in theory an IContentFinder can return true yet set no document + // but none of the substitued finders (see SubstituteFinder) do it. + + // do NOT set docRequest.PublishedContent again here + // as it would clear any template that the finder might have set + + LogHelper.Debug("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id); + if (docRequest.Is404) + LogHelper.Debug("Finder '{0}' set status to 404.", () => finderName); + + LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => docRequest.PublishedContent.Id); + return; + } + + // else it's a legacy handler, run + + // can't find a document => exit + if (handler.Execute(url) == false || handler.redirectID <= 0) + return; + + // found a document ID => ensure it's a valid document + var redirectId = handler.redirectID; + docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId); + + if (docRequest.HasPublishedContent == false) + { + // the handler said it could handle the url, and returned a content ID + // yet that content ID is invalid... exit. + + LogHelper.Debug("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId); + return; + } + + // found a valid document => return + + LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId); + + if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404) + { + LogHelper.Debug("Handler '{0}' set status code to 404.", () => handlerName); + docRequest.Is404 = true; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs index 4287b215d3..36d4a55bc9 100644 --- a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs +++ b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Web; using System.Xml; using System.Reflection; +using Umbraco.Core; using Umbraco.Core.Logging; +using umbraco.interfaces; namespace Umbraco.Web.Routing { @@ -36,14 +39,14 @@ namespace Umbraco.Web.Routing return url; // code from requestModule.UmbracoRewrite - string tmp = httpContext.Request.Path.ToLower(); + var tmp = httpContext.Request.Path.ToLower(); // note: requestModule.UmbracoRewrite also did some stripping of &umbPage // from the querystring... that was in v3.x to fix some issues with pre-forms // auth. Paul Sterling confirmed in jan. 2013 that we can get rid of it. // code from requestHandler.cleanUrl - string root = Core.IO.SystemDirectories.Root.ToLower(); + var root = Core.IO.SystemDirectories.Root.ToLower(); if (!string.IsNullOrEmpty(root) && tmp.StartsWith(root)) tmp = tmp.Substring(root.Length); tmp = tmp.TrimEnd('/'); @@ -55,7 +58,7 @@ namespace Umbraco.Web.Routing // code from UmbracoDefault.Page_PreInit if (tmp != "" && httpContext.Request["umbPageID"] == null) { - string tryIntParse = tmp.Replace("/", "").Replace(".aspx", string.Empty); + var tryIntParse = tmp.Replace("/", "").Replace(".aspx", string.Empty); int result; if (int.TryParse(tryIntParse, out result)) tmp = tmp.Replace(".aspx", string.Empty); @@ -77,7 +80,8 @@ namespace Umbraco.Web.Routing return tmp; } - static IEnumerable _customHandlerTypes; + private static IEnumerable _customHandlerTypes; + private static Type _customLastChanceHandlerType; static void InitializeNotFoundHandlers() { @@ -87,6 +91,8 @@ namespace Umbraco.Web.Routing LogHelper.Debug("Registering custom handlers."); var customHandlerTypes = new List(); + Type customLastChanceHandlerType = null; + var hasLast = false; var customHandlers = new XmlDocument(); customHandlers.Load(Core.IO.IOHelper.MapPath(Core.IO.SystemFiles.NotFoundhandlersConfig)); @@ -96,12 +102,23 @@ namespace Umbraco.Web.Routing var assemblyName = n.Attributes.GetNamedItem("assembly").Value; var typeName = n.Attributes.GetNamedItem("type").Value; - string ns = assemblyName; + var ns = assemblyName; var nsAttr = n.Attributes.GetNamedItem("namespace"); - if (nsAttr != null && !string.IsNullOrWhiteSpace(nsAttr.Value)) + if (nsAttr != null && string.IsNullOrWhiteSpace(nsAttr.Value) == false) ns = nsAttr.Value; - LogHelper.Debug("Registering '{0}.{1},{2}'.", () => ns, () => typeName, () => assemblyName); + var lcAttr = n.Attributes.GetNamedItem("last"); + var last = lcAttr != null && lcAttr.Value != null && lcAttr.Value.InvariantEquals("true"); + + if (last) // there can only be one last chance handler in the config file + { + if (hasLast) + throw new Exception(); + hasLast = true; + } + + LogHelper.Debug("Registering '{0}.{1},{2}'{3}", () => ns, () => typeName, () => assemblyName, + () => last ? " (last)." : "."); Type type = null; try @@ -114,19 +131,79 @@ namespace Umbraco.Web.Routing LogHelper.Error("Error registering handler, ignoring.", e); } - if (type != null) + if (type == null) continue; + + if (last) + _customLastChanceHandlerType = type; + else customHandlerTypes.Add(type); } - _customHandlerTypes = customHandlerTypes; + _customHandlerTypes = customHandlerTypes.ToArray(); } - public static IEnumerable CustomHandlerTypes + public static IEnumerable GetNotFoundHandlers() { - get + // instanciate new handlers + // using definition cache + + var handlers = new List(); + + foreach (var type in _customHandlerTypes) { - return _customHandlerTypes; + try + { + var handler = Activator.CreateInstance(type) as INotFoundHandler; + if (handler != null) + handlers.Add(handler); + } + catch (Exception e) + { + LogHelper.Error(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e); + } } + + return handlers; } + + public static bool IsNotFoundHandlerEnabled() + { + return _customHandlerTypes.Contains(typeof (T)); + } + + public static INotFoundHandler GetNotFoundLastChanceHandler() + { + if (_customLastChanceHandlerType == null) return null; + + try + { + var handler = Activator.CreateInstance(_customLastChanceHandlerType) as INotFoundHandler; + if (handler != null) + return handler; + } + catch (Exception e) + { + LogHelper.Error(string.Format("Error instanciating handler {0}, ignoring.", _customLastChanceHandlerType.FullName), e); + } + + return null; + } + + public static IContentFinder SubsituteFinder(INotFoundHandler handler) + { + IContentFinder finder = null; + + if (handler is global::umbraco.SearchForAlias) + finder = new ContentFinderByUrlAlias(); + else if (handler is global::umbraco.SearchForProfile) + finder = new ContentFinderByProfile(); + else if (handler is global::umbraco.SearchForTemplate) + finder = new ContentFinderByNiceUrlAndTemplate(); + else if (handler is global::umbraco.handle404) + finder = new ContentFinderByLegacy404(); + + return finder; + } + } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c8ab6c7f95..d65bcae0e6 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -292,6 +292,7 @@ + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index b2507f2174..35869fb76b 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -302,23 +302,31 @@ namespace Umbraco.Web typeof(DefaultUrlProvider) ); - // the legacy 404 will run from within ContentFinderByNotFoundHandlers below - // so for the time being there is no last chance finder - ContentLastChanceFinderResolver.Current = new ContentLastChanceFinderResolver(); + ContentLastChanceFinderResolver.Current = new ContentLastChanceFinderResolver( + // handled by ContentLastChanceFinderByNotFoundHandlers for the time being + // soon as we get rid of INotFoundHandler support, we must enable this + //new ContentFinderByLegacy404() + + // implement INotFoundHandler support... remove once we get rid of it + new ContentLastChanceFinderByNotFoundHandlers()); ContentFinderResolver.Current = new ContentFinderResolver( - // add all known resolvers in the correct order, devs can then modify this list - // on application startup either by binding to events or in their own global.asax - typeof (ContentFinderByPageIdQuery), - typeof (ContentFinderByNiceUrl), - typeof (ContentFinderByIdPath), - // these will be handled by ContentFinderByNotFoundHandlers - // so they can be enabled/disabled even though resolvers are not public yet - //typeof (ContentFinderByNiceUrlAndTemplate), - //typeof (ContentFinderByProfile), - //typeof (ContentFinderByUrlAlias), - typeof (ContentFinderByNotFoundHandlers) - ); + // all built-in finders in the correct order, devs can then modify this list + // on application startup via an application event handler. + typeof (ContentFinderByPageIdQuery), + typeof (ContentFinderByNiceUrl), + typeof (ContentFinderByIdPath), + + // these will be handled by ContentFinderByNotFoundHandlers so they can be enabled/disabled + // via the config file... soon as we get rid of INotFoundHandler support, we must enable + // them here. + //typeof (ContentFinderByNiceUrlAndTemplate), + //typeof (ContentFinderByProfile), + //typeof (ContentFinderByUrlAlias), + + // implement INotFoundHandler support... remove once we get rid of it + typeof (ContentFinderByNotFoundHandlers) + ); SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper()); From 4d5a8298b0fc42c23d81c4452558d14cb2be0e43 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 29 Aug 2013 16:29:03 +0200 Subject: [PATCH 08/18] U4-2549 - fix it differently --- .../config/404handlers.Release.config | 2 +- src/Umbraco.Web.UI/config/404handlers.config | 2 +- .../Routing/NotFoundHandlerHelper.cs | 51 +++++++++++-------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Web.UI/config/404handlers.Release.config b/src/Umbraco.Web.UI/config/404handlers.Release.config index 770f7ca64b..11bdbaf8ef 100644 --- a/src/Umbraco.Web.UI/config/404handlers.Release.config +++ b/src/Umbraco.Web.UI/config/404handlers.Release.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/404handlers.config b/src/Umbraco.Web.UI/config/404handlers.config index b6dd88fa8b..69bf6f1dd0 100644 --- a/src/Umbraco.Web.UI/config/404handlers.config +++ b/src/Umbraco.Web.UI/config/404handlers.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs index 36d4a55bc9..c7af4003aa 100644 --- a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs +++ b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs @@ -91,14 +91,19 @@ namespace Umbraco.Web.Routing LogHelper.Debug("Registering custom handlers."); var customHandlerTypes = new List(); - Type customLastChanceHandlerType = null; - var hasLast = false; + Type customHandlerType = null; var customHandlers = new XmlDocument(); customHandlers.Load(Core.IO.IOHelper.MapPath(Core.IO.SystemFiles.NotFoundhandlersConfig)); foreach (XmlNode n in customHandlers.DocumentElement.SelectNodes("notFound")) { + if (customHandlerType != null) + { + LogHelper.Debug("Registering '{0}'.", () => customHandlerType.FullName); + customHandlerTypes.Add(customHandlerType); + } + var assemblyName = n.Attributes.GetNamedItem("assembly").Value; var typeName = n.Attributes.GetNamedItem("type").Value; @@ -107,36 +112,40 @@ namespace Umbraco.Web.Routing if (nsAttr != null && string.IsNullOrWhiteSpace(nsAttr.Value) == false) ns = nsAttr.Value; - var lcAttr = n.Attributes.GetNamedItem("last"); - var last = lcAttr != null && lcAttr.Value != null && lcAttr.Value.InvariantEquals("true"); + LogHelper.Debug("Configured: '{0}.{1},{2}'.", () => ns, () => typeName, () => assemblyName); - if (last) // there can only be one last chance handler in the config file - { - if (hasLast) - throw new Exception(); - hasLast = true; - } - - LogHelper.Debug("Registering '{0}.{1},{2}'{3}", () => ns, () => typeName, () => assemblyName, - () => last ? " (last)." : "."); - - Type type = null; + customHandlerType = null; try { var assembly = Assembly.Load(new AssemblyName(assemblyName)); - type = assembly.GetType(ns + "." + typeName); + customHandlerType = assembly.GetType(ns + "." + typeName); } catch (Exception e) { - LogHelper.Error("Error registering handler, ignoring.", e); + LogHelper.Error("Error: could not load handler, ignoring.", e); } + } - if (type == null) continue; + // what shall we do with the last one, assuming it's not null? + // if the last chance finder wants a handler, then use the last one as the last chance handler + // else assume that the last one is a normal handler since noone else wants it, and add it to the list + if (customHandlerType != null) + { + var lastChanceFinder = ContentLastChanceFinderResolver.Current.Finder; // can be null + var finderWantsHandler = lastChanceFinder != null && + lastChanceFinder.GetType() == typeof(ContentLastChanceFinderByNotFoundHandlers); - if (last) - _customLastChanceHandlerType = type; + if (finderWantsHandler) + { + LogHelper.Debug("Registering '{0}' as \"last chance\" handler.", () => customHandlerType.FullName); + _customLastChanceHandlerType = customHandlerType; + } else - customHandlerTypes.Add(type); + { + LogHelper.Debug("Registering '{0}'.", () => customHandlerType.FullName); + customHandlerTypes.Add(customHandlerType); + _customLastChanceHandlerType = null; + } } _customHandlerTypes = customHandlerTypes.ToArray(); From d22dbb4654ed820d7ab71df1efc1214f479d2b7e Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 29 Aug 2013 12:38:14 +1000 Subject: [PATCH 09/18] Fixes U4-2734 Don't set the cache references to null when disposing the UmbracoContext --- src/Umbraco.Web/UmbracoContext.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index abec9e4076..17751b4077 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -370,8 +370,14 @@ namespace Umbraco.Web _umbracoContext = null; //ensure not to dispose this! Application = null; - ContentCache = null; - MediaCache = null; + + //Before we set these to null but in fact these are application lifespan singletons so + //there's no reason we need to set them to null and this also caused a problem with packages + //trying to access the cache properties on RequestEnd. + //http://issues.umbraco.org/issue/U4-2734 + //http://our.umbraco.org/projects/developer-tools/301-url-tracker/version-2/44327-Issues-with-URL-Tracker-in-614 + //ContentCache = null; + //MediaCache = null; } } } \ No newline at end of file From a1cae3f28670df3d2725b2dcf230fa84c5ef8f99 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 30 Aug 2013 10:52:58 +0200 Subject: [PATCH 10/18] Fixes U4-2731 Document.BeforeMove + Document.AfterMove Doesn't get fired. Using the legacy Document and Media classes for the .Move and .Copy calls as they already use the new services under the hood, so shouldn't make any noticeable difference except that the legacy events will also work. Signed-off-by: Sebastiaan Janssen --- .../umbraco/dialogs/moveOrCopy.aspx.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs index ccd6c6481d..86446782fe 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs @@ -272,11 +272,15 @@ namespace umbraco.dialogs { if (CurrentApp == Constants.Applications.Content) { - Services.ContentService.Move((IContent)currContent, Request.GetItemAs("copyTo"), getUser().Id); + //Backwards comp. change, so old events are fired #U4-2731 + var doc = new Document(currContent as IContent); + doc.Move(Request.GetItemAs("copyTo")); } else { - Services.MediaService.Move((IMedia)currContent, Request.GetItemAs("copyTo"), getUser().Id); + //Backwards comp. change, so old events are fired #U4-2731 + var media = new umbraco.cms.businesslogic.media.Media(currContent as IMedia); + media.Move(Request.GetItemAs("copyTo")); library.ClearLibraryCacheForMedia(currContent.Id); } @@ -290,7 +294,9 @@ namespace umbraco.dialogs { //NOTE: We ONLY support Copy on content not media for some reason. - var newContent = Services.ContentService.Copy((IContent)currContent, Request.GetItemAs("copyTo"), RelateDocuments.Checked, getUser().Id); + //Backwards comp. change, so old events are fired #U4-2731 + var newContent = new Document(currContent as IContent); + newContent.Copy(Request.GetItemAs("copyTo"), getUser(), RelateDocuments.Checked); feedback.Text = ui.Text("moveOrCopy", "copyDone", nodes, getUser()) + "

" + ui.Text("closeThisWindow") + ""; feedback.type = uicontrols.Feedback.feedbacktype.success; From 5640daff321958c70cf7993677d3eaab82170f39 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 30 Aug 2013 10:50:43 +0200 Subject: [PATCH 11/18] Fixes U4-2752 ContentService.DeleteVersion and DeleteVersions fail Signed-off-by: Sebastiaan Janssen --- .../Repositories/ContentRepository.cs | 24 +++++++++++++++++-- .../Repositories/MediaRepository.cs | 4 ++-- .../Repositories/VersionableRepositoryBase.cs | 8 +++---- .../Services/ContentServiceTests.cs | 16 +++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 870ed2cc00..e0dde68a6c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -170,11 +170,31 @@ namespace Umbraco.Core.Persistence.Repositories return content; } + public override void DeleteVersion(Guid versionId) + { + var sql = new Sql() + .Select("*") + .From() + .InnerJoin().On(left => left.VersionId, right => right.VersionId) + .Where(x => x.VersionId == versionId) + .Where(x => x.Newest == true); + var dto = Database.Fetch(sql).FirstOrDefault(); + + if(dto == null) return; + + using (var transaction = Database.GetTransaction()) + { + PerformDeleteVersion(dto.NodeId, versionId); + + transaction.Complete(); + } + } + protected override void PerformDeleteVersion(int id, Guid versionId) { Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index e27fac1ba3..8292fc8977 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -176,8 +176,8 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PerformDeleteVersion(int id, Guid versionId) { Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); } #endregion diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 0ed84582fa..9b9160b66a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -43,8 +43,8 @@ namespace Umbraco.Core.Persistence.Repositories public virtual void DeleteVersion(Guid versionId) { - var dto = Database.FirstOrDefault("WHERE versionId = @VersionId AND newest = @Newest", new { VersionId = versionId, Newest = false }); - Mandate.That(dto != null); + var dto = Database.FirstOrDefault("WHERE versionId = @VersionId", new { VersionId = versionId }); + if(dto == null) return; using (var transaction = Database.GetTransaction()) { @@ -56,8 +56,8 @@ namespace Umbraco.Core.Persistence.Repositories public virtual void DeleteVersions(int id, DateTime versionDate) { - var list = Database.Fetch("WHERE nodeId = @Id AND VersionDate < @VersionDate", new { Id = id, VersionDate = versionDate }); - Mandate.That(list.Any()); + var list = Database.Fetch("WHERE ContentId = @Id AND VersionDate < @VersionDate", new { Id = id, VersionDate = versionDate }); + if (list.Any() == false) return; using (var transaction = Database.GetTransaction()) { diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 522f608272..a8d3f6d30c 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -861,6 +861,22 @@ namespace Umbraco.Tests.Services Assert.That(sut.GetValue("imgCropper"), Is.Empty); } + [Test] + public void Can_Delete_Previous_Versions_Not_Latest() + { + // Arrange + var contentService = ServiceContext.ContentService; + var content = contentService.GetById(1049); + var version = content.Version; + + // Act + contentService.DeleteVersion(1049, version, true, 0); + var sut = contentService.GetById(1049); + + // Assert + Assert.That(sut.Version, Is.EqualTo(version)); + } + private IEnumerable CreateContentHierarchy() { var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); From 7765acb130eafc8b0bc01afdc2c6aeeedf5b7dde Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 1 Sep 2013 05:22:51 -0700 Subject: [PATCH 12/18] Merge pull request #124 from AndyButland/wip-u4-2759 U4-2759 - member authorise attribute was not restricting for group --- src/Umbraco.Web/Security/WebSecurity.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index f45ff9c30e..a7a57c3c32 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -70,15 +70,15 @@ namespace Umbraco.Web.Security var allowGroupsList = allowGroups as IList ?? allowGroups.ToList(); if (allowAction && allowGroupsList.Any(allowGroup => allowGroup != string.Empty)) { - // Allow only if member's type is in list + // Allow only if member is assigned to a group in the list var groups = Roles.GetRolesForUser(member.LoginName); - allowAction = groups.Select(s => s.ToLowerInvariant()).Intersect(groups.Select(myGroup => myGroup.ToLowerInvariant())).Any(); + allowAction = allowGroupsList.Select(s => s.ToLowerInvariant()).Intersect(groups.Select(myGroup => myGroup.ToLowerInvariant())).Any(); } // If specific members defined, check member is of one of those if (allowAction && allowMembers.Any()) { - // Allow only if member's type is in list + // Allow only if member's Id is in the list allowAction = allowMembers.Contains(member.Id); } } From 5478de911b88e4908c6bc4860947ff3d3f552585 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 1 Sep 2013 05:34:18 -0700 Subject: [PATCH 13/18] Merge pull request #115 from stocksr/6.2.0 U4-516 - Fix DatePicker with time default value --- .../umbraco_client/DateTimePicker/timepicker.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco_client/DateTimePicker/timepicker.js b/src/Umbraco.Web.UI/umbraco_client/DateTimePicker/timepicker.js index af27a79d43..f5034b4707 100644 --- a/src/Umbraco.Web.UI/umbraco_client/DateTimePicker/timepicker.js +++ b/src/Umbraco.Web.UI/umbraco_client/DateTimePicker/timepicker.js @@ -62,9 +62,19 @@ $.datepicker._connectDatepicker = function(target, inst) { */ $.datepicker._showDatepickerOverride = $.datepicker._showDatepicker; $.datepicker._showDatepicker = function (input) { + // keep the current value + var originalval = input.value; + + // Keep the first 10 chars for now yyyy-mm-dd - this removes the time part which was breaking the standardDatePicker parsing code + input.value = originalval.length>10 ? originalval.substring(0, 10) : originalval; + + // Call the original method which will show the datepicker $.datepicker._showDatepickerOverride(input); + // Put it back + input.value = originalval; + input = input.target || input; // find from button/image trigger From 2111a5e31e906630998c827cdd7d4238a288be9a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 1 Sep 2013 17:21:19 +0200 Subject: [PATCH 14/18] Fix MySQL install failing on UmbracoServer table --- .../DatabaseModelDefinitions/DefinitionFactory.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs index f987e69b3d..9b08279716 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs @@ -3,6 +3,7 @@ using System.Data; using System.Linq; using System.Reflection; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { @@ -107,6 +108,12 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions var constraintAttribute = propertyInfo.FirstAttribute(); if (constraintAttribute != null) { + //Special case for MySQL as it can't have multiple default DateTime values, which + //is what the umbracoServer table definition is trying to create + if (SqlSyntaxContext.SqlSyntaxProvider is MySqlSyntaxProvider && definition.TableName == "umbracoServer" && + definition.TableName.ToLowerInvariant() == "lastNotifiedDate".ToLowerInvariant()) + return definition; + definition.ConstraintName = constraintAttribute.Name ?? string.Empty; definition.DefaultValue = constraintAttribute.Default ?? string.Empty; } From da1c33a0349503caa460c3d5bbc3b25114523202 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 2 Sep 2013 12:57:56 +1000 Subject: [PATCH 15/18] Fixes unit test --- .gitignore | 3 +++ src/Umbraco.Tests/Models/DataValueSetterTests.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 9496a86e70..da24566589 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,6 @@ build/_BuildOutput/ tools/NDepend/ src/*.vspx src/*.psess +NDependOut/* +QueryResult.htm +*.ndproj diff --git a/src/Umbraco.Tests/Models/DataValueSetterTests.cs b/src/Umbraco.Tests/Models/DataValueSetterTests.cs index db59217166..25b0f53883 100644 --- a/src/Umbraco.Tests/Models/DataValueSetterTests.cs +++ b/src/Umbraco.Tests/Models/DataValueSetterTests.cs @@ -66,6 +66,8 @@ namespace Umbraco.Tests.Models var dataTypeId = Guid.NewGuid(); var dataTypeData = MockRepository.GenerateMock(); + //needs to have a value for SetValue to be called + dataTypeData.Stub(data => data.Value).Return(string.Empty); dataTypeData .Stub(data => data.ToXMl(Arg.Is.Anything)) .Return(null) // you have to call Return() even though we're about to override it From 5989d19f7f25c06393e7fa81a2b0417479ab6fe5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 2 Sep 2013 13:27:48 +1000 Subject: [PATCH 16/18] Re-fixes the issue with using IDataValueSetter.SetValue on the DefaultData implementation and ensures that when there is a null value that it reverts to an empty string since this was what the default value was in the Value getter of DefaultData when there was no value. Have added a couple unit tests to support. --- src/Umbraco.Core/Models/PropertyExtensions.cs | 2 +- src/Umbraco.Tests/Models/DataValueSetterTests.cs | 16 ++++++++++++++-- .../businesslogic/datatype/DefaultData.cs | 11 +++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Models/PropertyExtensions.cs b/src/Umbraco.Core/Models/PropertyExtensions.cs index 2ef594c8c2..ae01532c87 100644 --- a/src/Umbraco.Core/Models/PropertyExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyExtensions.cs @@ -43,7 +43,7 @@ namespace Umbraco.Core.Models //This seems to fail during testing //SD: With the new null checks below, this shouldn't fail anymore. var dt = property.PropertyType.DataType(property.Id, dataTypeService); - if (dt != null && dt.Data != null && dt.Data.Value != null) + if (dt != null && dt.Data != null) { //We've already got the value for the property so we're going to give it to the // data type's data property so it doesn't go re-look up the value from the db again. diff --git a/src/Umbraco.Tests/Models/DataValueSetterTests.cs b/src/Umbraco.Tests/Models/DataValueSetterTests.cs index 25b0f53883..891c53dd0d 100644 --- a/src/Umbraco.Tests/Models/DataValueSetterTests.cs +++ b/src/Umbraco.Tests/Models/DataValueSetterTests.cs @@ -66,8 +66,7 @@ namespace Umbraco.Tests.Models var dataTypeId = Guid.NewGuid(); var dataTypeData = MockRepository.GenerateMock(); - //needs to have a value for SetValue to be called - dataTypeData.Stub(data => data.Value).Return(string.Empty); + dataTypeData .Stub(data => data.ToXMl(Arg.Is.Anything)) .Return(null) // you have to call Return() even though we're about to override it @@ -100,5 +99,18 @@ namespace Umbraco.Tests.Models ((IDataValueSetter)dataTypeData).AssertWasCalled(setter => setter.SetValue("Hello world", DataTypeDatabaseType.Nvarchar.ToString())); } + [TestCase(DataTypeDatabaseType.Nvarchar)] + [TestCase(DataTypeDatabaseType.Date)] + [TestCase(DataTypeDatabaseType.Integer)] + [TestCase(DataTypeDatabaseType.Ntext)] + public void DefaultData_SetValue_Ensures_Empty_String_When_Null_Value_Any_Data_Type(DataTypeDatabaseType type) + { + var defaultData = new DefaultData(MockRepository.GenerateMock()); + + ((IDataValueSetter)defaultData).SetValue(null, type.ToString()); + + Assert.AreEqual(string.Empty, defaultData.Value); + } + } } \ No newline at end of file diff --git a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs index a974fda7c2..471fcc9479 100644 --- a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs +++ b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs @@ -65,6 +65,17 @@ namespace umbraco.cms.businesslogic.datatype /// void IDataValueSetter.SetValue(object val, string strDbType) { + //We need to ensure that val is not a null value, if it is then we'll convert this to an empty string. + //The reason for this is because by default the DefaultData.Value property returns an empty string when + // there is no value, this is based on the PropertyDataDto.GetValue return value which defaults to an + // empty string (which is called from this class's method LoadValueFromDatabase). + //Some legacy implementations of DefaultData are expecting an empty string when there is + // no value so we need to keep this consistent. + if (val == null) + { + val = string.Empty; + } + _value = val; //now that we've set our value, we can update our BaseDataType object with the correct values from the db //instead of making it query for itself. This is a peformance optimization enhancement. From 7c28e1aeba4f34c7aff2a600108f258990815160 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 2 Sep 2013 10:56:42 +0200 Subject: [PATCH 17/18] Fixed U4-2763 Content rollback generates duplicates --- .../Repositories/ContentRepository.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index e0dde68a6c..e34fed0c56 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -360,19 +360,19 @@ namespace Umbraco.Core.Persistence.Repositories } } + //Look up (newest) entries by id in cmsDocument table to set newest = false + //NOTE: This is only relevant when a new version is created, which is why its done inside this if-statement. + var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true }); + foreach (var documentDto in documentDtos) + { + var docDto = documentDto; + docDto.Newest = false; + Database.Update(docDto); + } + var contentVersionDto = dto.ContentVersionDto; if (shouldCreateNewVersion) { - //Look up (newest) entries by id in cmsDocument table to set newest = false - //NOTE: This is only relevant when a new version is created, which is why its done inside this if-statement. - var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true }); - foreach (var documentDto in documentDtos) - { - var docDto = documentDto; - docDto.Newest = false; - Database.Update(docDto); - } - //Create a new version - cmsContentVersion //Assumes a new Version guid and Version date (modified date) has been set Database.Insert(contentVersionDto); From 173ac65eb57d9305decadaa80e72054a8e1e6b49 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 2 Sep 2013 11:41:13 +0200 Subject: [PATCH 18/18] Remove misleading comment as this has been moved out of the if statement --- src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index e34fed0c56..f290b5b54a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -361,7 +361,6 @@ namespace Umbraco.Core.Persistence.Repositories } //Look up (newest) entries by id in cmsDocument table to set newest = false - //NOTE: This is only relevant when a new version is created, which is why its done inside this if-statement. var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true }); foreach (var documentDto in documentDtos) {