diff --git a/.hgignore b/.hgignore index b50380be24..1edbdf19cc 100644 --- a/.hgignore +++ b/.hgignore @@ -55,3 +55,4 @@ src/Umbraco.Web.UI/Views/*.cshtml src/Umbraco.Web.UI/Views/*.vbhtml src/Umbraco.Tests/config/umbracoSettings.config src/Umbraco.Web.UI/App_Plugins/* +src/Umbraco.Web.UI/Views/* diff --git a/src/Umbraco.Core/DisposableTimer.cs b/src/Umbraco.Core/DisposableTimer.cs index 5269244609..f60c3a5634 100644 --- a/src/Umbraco.Core/DisposableTimer.cs +++ b/src/Umbraco.Core/DisposableTimer.cs @@ -87,7 +87,7 @@ namespace Umbraco.Core /// public static DisposableTimer DebugDuration(Type loggerType, string startMessage, string completeMessage) { - LogHelper.Info(loggerType, () => startMessage); + LogHelper.Debug(loggerType, () => startMessage); return new DisposableTimer(x => LogHelper.Debug(loggerType, () => completeMessage + " (took " + x + "ms)")); } diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index d5a5e2e452..32e15d826d 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -13,7 +13,7 @@ using Umbraco.Core.Logging; namespace Umbraco.Core.IO { - internal static class IOHelper + public static class IOHelper { private static string _rootDir = ""; @@ -52,7 +52,7 @@ namespace Umbraco.Core.IO } [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")] - public static string ResolveUrlsFromTextString(string text) + internal static string ResolveUrlsFromTextString(string text) { if (UmbracoSettings.ResolveUrlsFromTextString) { @@ -117,7 +117,7 @@ namespace Umbraco.Core.IO } //use a tilde character instead of the complete path - public static string ReturnPath(string settingsKey, string standardPath, bool useTilde) + internal static string ReturnPath(string settingsKey, string standardPath, bool useTilde) { string retval = ConfigurationManager.AppSettings[settingsKey]; @@ -127,14 +127,12 @@ namespace Umbraco.Core.IO return retval.TrimEnd('/'); } - - public static string ReturnPath(string settingsKey, string standardPath) + internal static string ReturnPath(string settingsKey, string standardPath) { return ReturnPath(settingsKey, standardPath, false); } - /// /// Validates if the current filepath matches a directory where the user is allowed to edit a file /// diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index 521407898a..e7f6b29c60 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -86,8 +86,9 @@ namespace Umbraco.Core.IO } } + //have changed to internal so nobody uses this anymore since this is a new class. [Obsolete("Please use MacroScripts instead!", true)] - public static string Python + internal static string Python { get { diff --git a/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs b/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs index 582ec50b9e..27f2307bd2 100644 --- a/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.ObjectResolution /// TODO: However, it would make much more sense to do this and would speed up the application plus this would make the GetById method much easier. /// protected LegacyTransientObjectsResolver(IEnumerable refreshers) - : base(ObjectLifetimeScope.Transient) // false = new objects every time + : base(ObjectLifetimeScope.Transient) // new objects every time { foreach (var l in refreshers) { diff --git a/src/Umbraco.Core/XmlHelper.cs b/src/Umbraco.Core/XmlHelper.cs index 10b2d8a09d..0a3d16e9c8 100644 --- a/src/Umbraco.Core/XmlHelper.cs +++ b/src/Umbraco.Core/XmlHelper.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core /// /// The XmlHelper class contains general helper methods for working with xml in umbraco. /// - internal class XmlHelper + public class XmlHelper { /// @@ -20,7 +20,7 @@ namespace Umbraco.Core /// The text. /// The XML doc. /// - public static XmlNode ImportXmlNodeFromText(string text, ref XmlDocument xmlDoc) + internal static XmlNode ImportXmlNodeFromText(string text, ref XmlDocument xmlDoc) { xmlDoc.LoadXml(text); return xmlDoc.FirstChild; @@ -92,7 +92,7 @@ namespace Umbraco.Core /// /// The XmlNode. /// the value as a string - public static string GetNodeValue(XmlNode n) + internal static string GetNodeValue(XmlNode n) { var value = string.Empty; if (n == null || n.FirstChild == null) @@ -108,7 +108,7 @@ namespace Umbraco.Core /// /// true if the specified string appears to be XML; otherwise, false. /// - public static bool CouldItBeXml(string xml) + internal static bool CouldItBeXml(string xml) { if (!string.IsNullOrEmpty(xml)) { @@ -131,7 +131,7 @@ namespace Umbraco.Core /// Name of the root. /// Name of the element. /// Returns an System.Xml.XmlDocument representation of the delimited string data. - public static XmlDocument Split(string data, string[] separator, string rootName, string elementName) + internal static XmlDocument Split(string data, string[] separator, string rootName, string elementName) { return Split(new XmlDocument(), data, separator, rootName, elementName); } @@ -145,7 +145,7 @@ namespace Umbraco.Core /// Name of the root node. /// Name of the element node. /// Returns an System.Xml.XmlDocument representation of the delimited string data. - public static XmlDocument Split(XmlDocument xml, string data, string[] separator, string rootName, string elementName) + internal static XmlDocument Split(XmlDocument xml, string data, string[] separator, string rootName, string elementName) { // load new XML document. xml.LoadXml(string.Concat("<", rootName, "/>")); @@ -174,7 +174,7 @@ namespace Umbraco.Core /// /// /// - public static Dictionary GetAttributesFromElement(string tag) + internal static Dictionary GetAttributesFromElement(string tag) { var m = Regex.Matches(tag, "(?\\S*)=\"(?[^\"]*)\"", diff --git a/test/umbraco.Test/DictionaryTest.cs b/src/Umbraco.Tests/BusinessLogic/DictionaryTest.cs similarity index 85% rename from test/umbraco.Test/DictionaryTest.cs rename to src/Umbraco.Tests/BusinessLogic/DictionaryTest.cs index b217c11ee2..a73e085e87 100644 --- a/test/umbraco.Test/DictionaryTest.cs +++ b/src/Umbraco.Tests/BusinessLogic/DictionaryTest.cs @@ -1,30 +1,39 @@ -using umbraco.cms.businesslogic; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Tests.TestHelpers; +using umbraco.cms.businesslogic; using System; using System.Xml; using umbraco.cms.businesslogic.language; using umbraco.BusinessLogic; using System.Linq; -namespace Umbraco.LegacyTests +namespace Umbraco.Tests.BusinessLogic { - + //TODO: This was ported over from the previous unit tests, need to make them work now :) /// ///This is a test class for Dictionary_DictionaryItemTest and is intended ///to contain all Dictionary_DictionaryItemTest Unit Tests /// - [TestClass()] - public class DictionaryTest + [TestFixture] + public class DictionaryTest : BaseWebTest { - [TestMethod()] + public override void Initialize() + { + base.Initialize(); + + CreateNew(); + } + + [Test()] public void Dictionary_Get_Top_Level_Items() { var items = Dictionary.getTopMostItems; var d = CreateNew(); - Assert.AreEqual(items.Count() + 1, Dictionary.getTopMostItems.Count()); + Assert.AreEqual(items.Count() + 1, Dictionary.getTopMostItems.Count()); DeleteItem(d); } @@ -33,7 +42,7 @@ namespace Umbraco.LegacyTests /// Creates a new dictionary entry, adds values for all languages assigned, then deletes the /// entry and ensure that all other data is gone too. /// - [TestMethod()] + [Test()] public void Dictionary_Create_Add_Text_And_Delete() { var d = CreateNew(); @@ -55,7 +64,7 @@ namespace Umbraco.LegacyTests /// ///A test for IsTopMostItem /// - [TestMethod()] + [Test()] public void Dictionary_IsTopMostItem() { var parent = CreateNew(); @@ -64,7 +73,7 @@ namespace Umbraco.LegacyTests var childId = Dictionary.DictionaryItem.addKey("Test" + Guid.NewGuid().ToString("N"), "", parent.key); Assert.IsTrue(childId > 0); var child = new Dictionary.DictionaryItem(childId); - Assert.IsInstanceOfType(child, typeof(Dictionary.DictionaryItem)); + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(child)); Assert.IsTrue(parent.IsTopMostItem()); Assert.IsFalse(child.IsTopMostItem()); @@ -76,7 +85,7 @@ namespace Umbraco.LegacyTests /// /// Test the Parent and Children properties and ensures that the relationships work both ways /// - [TestMethod()] + [Test()] public void Dictionary_Parent_Child_Relationship() { var parent = CreateNew(); @@ -85,7 +94,7 @@ namespace Umbraco.LegacyTests var childId = Dictionary.DictionaryItem.addKey("Test" + Guid.NewGuid().ToString("N"), "", parent.key); Assert.IsTrue(childId > 0); var child = new Dictionary.DictionaryItem(childId); - Assert.IsInstanceOfType(child, typeof(Dictionary.DictionaryItem)); + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(child)); //set the parent relationship Assert.AreEqual(parent.id, child.Parent.id); @@ -94,7 +103,7 @@ namespace Umbraco.LegacyTests //test the child relationship Assert.IsTrue(parent.hasChildren); - Assert.AreEqual(1, parent.Children.Length); + Assert.AreEqual(1, parent.Children.Length); Assert.AreEqual(child.id, parent.Children.First().id); Assert.AreEqual(child.key, parent.Children.First().key); Assert.AreEqual(child.UniqueId, parent.Children.First().UniqueId); @@ -106,7 +115,7 @@ namespace Umbraco.LegacyTests /// /// Deletes a parent with existing children and ensures they are all gone. /// - [TestMethod()] + [Test()] public void Dictionary_Delete_Parent_With_Children() { var parent = CreateNew(); @@ -115,16 +124,16 @@ namespace Umbraco.LegacyTests var childId1 = Dictionary.DictionaryItem.addKey("Test" + Guid.NewGuid().ToString("N"), "", parent.key); Assert.IsTrue(childId1 > 0); var child1 = new Dictionary.DictionaryItem(childId1); - Assert.IsInstanceOfType(child1, typeof(Dictionary.DictionaryItem)); + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(child1)); //create a child var childId2 = Dictionary.DictionaryItem.addKey("Test" + Guid.NewGuid().ToString("N"), "", parent.key); Assert.IsTrue(childId2 > 0); var child2 = new Dictionary.DictionaryItem(childId2); - Assert.IsInstanceOfType(child2, typeof(Dictionary.DictionaryItem)); + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(child2)); Assert.IsTrue(parent.hasChildren); - Assert.AreEqual(2, parent.Children.Length); + Assert.AreEqual(2, parent.Children.Length); DeleteItem(parent); @@ -157,7 +166,7 @@ namespace Umbraco.LegacyTests /// /// Guid constructor test /// - [TestMethod()] + [Test()] public void Dictionary_Contructor_Guid() { var d = CreateNew(); @@ -174,7 +183,7 @@ namespace Umbraco.LegacyTests /// /// key constructor test /// - [TestMethod()] + [Test()] public void Dictionary_Contructor_Key() { var d = CreateNew(); @@ -191,7 +200,7 @@ namespace Umbraco.LegacyTests /// ///A test for ToXml /// - [TestMethod()] + [Test()] public void Dictionary_ToXml() { var d = CreateNew(); @@ -200,7 +209,7 @@ namespace Umbraco.LegacyTests var childId = Dictionary.DictionaryItem.addKey("Test" + Guid.NewGuid().ToString("N"), "", d.key); Assert.IsTrue(childId > 0); var child = new Dictionary.DictionaryItem(childId); - Assert.IsInstanceOfType(child, typeof(Dictionary.DictionaryItem)); + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(child)); var xml = new XmlDocument(); @@ -218,7 +227,7 @@ namespace Umbraco.LegacyTests /// ///A test to change the key of an element /// - [TestMethod()] + [Test()] public void Dictionary_Change_Key() { //System.Diagnostics.Debugger.Break(); @@ -247,14 +256,14 @@ namespace Umbraco.LegacyTests /// /// Tries to create a duplicate key and ensures it's not possible. /// - [TestMethod()] + [Test()] public void Dictionary_Attempt_Duplicate_Key() { var key = "Test" + Guid.NewGuid().ToString("N"); var d1Id = Dictionary.DictionaryItem.addKey(key, ""); Assert.IsTrue(d1Id > 0); var d1 = new Dictionary.DictionaryItem(d1Id); - Assert.IsInstanceOfType(d1, typeof(Dictionary.DictionaryItem)); + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(d1)); var alreadyExists = false; try @@ -273,13 +282,14 @@ namespace Umbraco.LegacyTests } #region Private methods + private Dictionary.DictionaryItem CreateNew() { var id = Dictionary.DictionaryItem.addKey("Test" + Guid.NewGuid().ToString("N"), ""); Assert.IsTrue(id > 0); var d = new Dictionary.DictionaryItem(id); - Assert.IsInstanceOfType(d, typeof(Dictionary.DictionaryItem)); + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(d)); return d; } @@ -314,7 +324,7 @@ namespace Umbraco.LegacyTests ///// /////A test for Import ///// - //[TestMethod()] + //[Test()] //public void ImportTest() //{ // XmlNode xmlData = null; // TODO: Initialize to an appropriate value @@ -329,7 +339,7 @@ namespace Umbraco.LegacyTests ///// /////A test for Import ///// - //[TestMethod()] + //[Test()] //public void ImportTest1() //{ // XmlNode xmlData = null; // TODO: Initialize to an appropriate value @@ -345,7 +355,7 @@ namespace Umbraco.LegacyTests ///// /////A test for Save ///// - //[TestMethod()] + //[Test()] //public void SaveTest() //{ // Guid id = new Guid(); // TODO: Initialize to an appropriate value @@ -359,7 +369,7 @@ namespace Umbraco.LegacyTests ///// /////A test for Value ///// - //[TestMethod()] + //[Test()] //public void ValueTest() //{ // Guid id = new Guid(); // TODO: Initialize to an appropriate value @@ -375,7 +385,7 @@ namespace Umbraco.LegacyTests ///// /////A test for Value ///// - //[TestMethod()] + //[Test()] //public void ValueTest1() //{ // Guid id = new Guid(); // TODO: Initialize to an appropriate value @@ -390,7 +400,7 @@ namespace Umbraco.LegacyTests ///// /////A test for addKey ///// - //[TestMethod()] + //[Test()] //public void addKeyTest() //{ // string key = string.Empty; // TODO: Initialize to an appropriate value @@ -405,7 +415,7 @@ namespace Umbraco.LegacyTests ///// /////A test for addKey ///// - //[TestMethod()] + //[Test()] //public void addKeyTest1() //{ // string key = string.Empty; // TODO: Initialize to an appropriate value @@ -421,7 +431,7 @@ namespace Umbraco.LegacyTests ///// /////A test for delete ///// - //[TestMethod()] + //[Test()] //public void deleteTest() //{ // Guid id = new Guid(); // TODO: Initialize to an appropriate value @@ -433,7 +443,7 @@ namespace Umbraco.LegacyTests ///// /////A test for hasKey ///// - //[TestMethod()] + //[Test()] //public void hasKeyTest() //{ // string key = string.Empty; // TODO: Initialize to an appropriate value @@ -447,7 +457,7 @@ namespace Umbraco.LegacyTests ///// /////A test for setValue ///// - //[TestMethod()] + //[Test()] //public void setValueTest() //{ // Guid id = new Guid(); // TODO: Initialize to an appropriate value @@ -461,7 +471,7 @@ namespace Umbraco.LegacyTests ///// /////A test for setValue ///// - //[TestMethod()] + //[Test()] //public void setValueTest1() //{ // Guid id = new Guid(); // TODO: Initialize to an appropriate value @@ -474,7 +484,7 @@ namespace Umbraco.LegacyTests ///// /////A test for Children ///// - //[TestMethod()] + //[Test()] //public void ChildrenTest() //{ // Guid id = new Guid(); // TODO: Initialize to an appropriate value @@ -487,7 +497,7 @@ namespace Umbraco.LegacyTests ///// /////A test for Parent ///// - //[TestMethod()] + //[Test()] //public void ParentTest() //{ // Guid id = new Guid(); // TODO: Initialize to an appropriate value @@ -500,7 +510,7 @@ namespace Umbraco.LegacyTests ///// /////A test for hasChildren ///// - //[TestMethod()] + //[Test()] //public void hasChildrenTest() //{ // Guid id = new Guid(); // TODO: Initialize to an appropriate value @@ -513,7 +523,7 @@ namespace Umbraco.LegacyTests ///// /////A test for id ///// - //[TestMethod()] + //[Test()] //public void idTest() //{ // Guid id = new Guid(); // TODO: Initialize to an appropriate value @@ -526,34 +536,5 @@ namespace Umbraco.LegacyTests #endregion - #region Initialize and Cleanup - // - //You can use the following additional attributes as you write your tests: - // - //Use ClassInitialize to run code before running the first test in the class - //[ClassInitialize()] - //public static void MyClassInitialize(TestContext testContext) - //{ - //} - // - //Use ClassCleanup to run code after all tests in a class have run - //[ClassCleanup()] - //public static void MyClassCleanup() - //{ - //} - // - //Use TestInitialize to run code before running each test - //[TestInitialize()] - //public void MyTestInitialize() - //{ - //} - // - //Use TestCleanup to run code after each test has run - //[TestCleanup()] - //public void MyTestCleanup() - //{ - //} - // - #endregion } } diff --git a/src/Umbraco.Tests/ContentStores/PublishContentStoreTests.cs b/src/Umbraco.Tests/ContentStores/PublishContentStoreTests.cs index d8f7831e3e..e4e19a6b18 100644 --- a/src/Umbraco.Tests/ContentStores/PublishContentStoreTests.cs +++ b/src/Umbraco.Tests/ContentStores/PublishContentStoreTests.cs @@ -5,10 +5,9 @@ using Umbraco.Core; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Routing; -using umbraco; using umbraco.BusinessLogic; -namespace Umbraco.Tests +namespace Umbraco.Tests.ContentStores { [TestFixture] public class PublishContentStoreTests diff --git a/test/umbraco.Test/IOHelperTest.cs b/src/Umbraco.Tests/IO/IOHelperTest.cs similarity index 58% rename from test/umbraco.Test/IOHelperTest.cs rename to src/Umbraco.Tests/IO/IOHelperTest.cs index ab4d382e95..d23d5cba5f 100644 --- a/test/umbraco.Test/IOHelperTest.cs +++ b/src/Umbraco.Tests/IO/IOHelperTest.cs @@ -1,7 +1,7 @@ -using umbraco.IO; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; +using Umbraco.Core.IO; -namespace Umbraco.LegacyTests +namespace Umbraco.Tests.IO { @@ -9,64 +9,14 @@ namespace Umbraco.LegacyTests ///This is a test class for IOHelperTest and is intended ///to contain all IOHelperTest Unit Tests /// - [TestClass()] + [TestFixture()] public class IOHelperTest { - - private TestContext testContextInstance; - - /// - ///Gets or sets the test context which provides - ///information about and functionality for the current test run. - /// - public TestContext TestContext - { - get - { - return testContextInstance; - } - set - { - testContextInstance = value; - } - } - - #region Additional test attributes - // - //You can use the following additional attributes as you write your tests: - // - //Use ClassInitialize to run code before running the first test in the class - //[ClassInitialize()] - //public static void MyClassInitialize(TestContext testContext) - //{ - //} - // - //Use ClassCleanup to run code after all tests in a class have run - //[ClassCleanup()] - //public static void MyClassCleanup() - //{ - //} - // - //Use TestInitialize to run code before running each test - //[TestInitialize()] - //public void MyTestInitialize() - //{ - //} - // - //Use TestCleanup to run code after each test has run - //[TestCleanup()] - //public void MyTestCleanup() - //{ - //} - // - #endregion - - /// ///A test for MapPath verifying that HttpContext method (which includes vdirs) matches non-HttpContext method /// - [TestMethod()] + [Test] public void IOHelper_MapPathTestVDirTraversal() { //System.Diagnostics.Debugger.Break(); @@ -84,9 +34,9 @@ namespace Umbraco.LegacyTests Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Root, true), IOHelper.MapPath(SystemDirectories.Root, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Scripts, true), IOHelper.MapPath(SystemDirectories.Scripts, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Umbraco, true), IOHelper.MapPath(SystemDirectories.Umbraco, false)); - Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Umbraco_client, true), IOHelper.MapPath(SystemDirectories.Umbraco_client, false)); - Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Usercontrols, true), IOHelper.MapPath(SystemDirectories.Usercontrols, false)); - Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Webservices, true), IOHelper.MapPath(SystemDirectories.Webservices, false)); + Assert.AreEqual(IOHelper.MapPath(SystemDirectories.UmbracoClient, true), IOHelper.MapPath(SystemDirectories.UmbracoClient, false)); + Assert.AreEqual(IOHelper.MapPath(SystemDirectories.UserControls, true), IOHelper.MapPath(SystemDirectories.UserControls, false)); + Assert.AreEqual(IOHelper.MapPath(SystemDirectories.WebServices, true), IOHelper.MapPath(SystemDirectories.WebServices, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Xslt, true), IOHelper.MapPath(SystemDirectories.Xslt, false)); } } diff --git a/src/Umbraco.Tests/Templates/MasterPageHelperTests.cs b/src/Umbraco.Tests/Templates/MasterPageHelperTests.cs new file mode 100644 index 0000000000..f52b7b68d9 --- /dev/null +++ b/src/Umbraco.Tests/Templates/MasterPageHelperTests.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using umbraco.cms.businesslogic.template; + +namespace Umbraco.Tests.Templates +{ + [TestFixture] + public class MasterPageHelperTests + { + + [TestCase(@"<%@ master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] + [TestCase(@"<%@ Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] + [TestCase(@"<%@Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] + [TestCase(@"<%@ Master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] + [TestCase(@"<%@master language=""C#"" masterpagefile=""~/masterpages/umbMaster.master"" autoeventwireup=""true"" %>")] + public void IsMasterPageSyntax(string design) + { + Assert.IsTrue(MasterPageHelper.IsMasterPageSyntax(design)); + } + + } +} diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index 1b1076cb08..2009d7d683 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Web.Routing; using System.Xml; @@ -14,7 +15,7 @@ using umbraco.cms.businesslogic.template; namespace Umbraco.Tests.TestHelpers { - [TestFixture] + [TestFixture, RequiresSTA] public abstract class BaseWebTest { @@ -22,6 +23,9 @@ namespace Umbraco.Tests.TestHelpers public virtual void Initialize() { TestHelper.SetupLog4NetForTests(); + + AppDomain.CurrentDomain.SetData("DataDirectory", TestHelper.CurrentAssemblyDirectory); + if (RequiresDbSetup) TestHelper.InitializeDatabase(); Resolution.Freeze(); @@ -36,6 +40,8 @@ namespace Umbraco.Tests.TestHelpers [TearDown] public virtual void TearDown() { + AppDomain.CurrentDomain.SetData("DataDirectory", null); + //reset the app context ApplicationContext.Current = null; Resolution.IsFrozen = false; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e9db4767eb..2441e5815e 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -56,12 +56,14 @@ + + @@ -86,6 +88,7 @@ + diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 057945415a..d62cc88e51 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -129,8 +129,8 @@ - true - false + true + WebForms diff --git a/src/Umbraco.Web.UI/umbraco_client/FolderBrowser/Css/folderbrowser.css b/src/Umbraco.Web.UI/umbraco_client/FolderBrowser/Css/folderbrowser.css index def770222a..15eb8c0804 100644 --- a/src/Umbraco.Web.UI/umbraco_client/FolderBrowser/Css/folderbrowser.css +++ b/src/Umbraco.Web.UI/umbraco_client/FolderBrowser/Css/folderbrowser.css @@ -167,6 +167,8 @@ .umbFolderBrowser .items li { display: inline-block; + list-style: none; + float: left; } .umbFolderBrowser .items li div { diff --git a/src/Umbraco.Web/DefaultPublishedContentStore.cs b/src/Umbraco.Web/DefaultPublishedContentStore.cs index c1d79283a7..1b8dfddc7c 100644 --- a/src/Umbraco.Web/DefaultPublishedContentStore.cs +++ b/src/Umbraco.Web/DefaultPublishedContentStore.cs @@ -161,7 +161,10 @@ namespace Umbraco.Web public bool HasContent(UmbracoContext umbracoContext) { - var node = GetXml(umbracoContext).SelectSingleNode(XPathStrings.RootDocuments); + var xml = GetXml(umbracoContext); + if (xml == null) + return false; + var node = xml.SelectSingleNode(XPathStrings.RootDocuments); return node != null; } diff --git a/src/Umbraco.Web/DefaultPublishedMediaStore.cs b/src/Umbraco.Web/DefaultPublishedMediaStore.cs index 51ec05b9f8..2283c2a18f 100644 --- a/src/Umbraco.Web/DefaultPublishedMediaStore.cs +++ b/src/Umbraco.Web/DefaultPublishedMediaStore.cs @@ -71,8 +71,17 @@ namespace Umbraco.Web var media = global::umbraco.library.GetMedia(id, true); if (media != null && media.Current != null) { - media.MoveNext(); - return ConvertFromXPathNavigator(media.Current); + if (media.MoveNext()) + { + var current = media.Current; + //error check + if (media.Current.MoveToFirstChild() && media.Current.Name.InvariantEquals("error")) + { + return null; + } + + return ConvertFromXPathNavigator(current); + } } return null; diff --git a/src/Umbraco.Web/Mvc/RenderViewPage.cs b/src/Umbraco.Web/Mvc/RenderViewPage.cs index 36ad6d51d4..8d3b450b75 100644 --- a/src/Umbraco.Web/Mvc/RenderViewPage.cs +++ b/src/Umbraco.Web/Mvc/RenderViewPage.cs @@ -51,17 +51,7 @@ namespace Umbraco.Web.Mvc /// /// Returns the a DynamicPublishedContent object /// - public dynamic CurrentPage { get; private set; } - - /// - /// Returns the dictionary value for the key specified - /// - /// - /// - public string GetDictionaryValue(string key) - { - return Umbraco.GetDictionaryValue(key); - } + public dynamic CurrentPage { get; private set; } private UmbracoHelper _helper; diff --git a/src/Umbraco.Web/UI/Controls/FolderBrowser.cs b/src/Umbraco.Web/UI/Controls/FolderBrowser.cs index b8569730b2..421d9c369d 100644 --- a/src/Umbraco.Web/UI/Controls/FolderBrowser.cs +++ b/src/Umbraco.Web/UI/Controls/FolderBrowser.cs @@ -112,16 +112,16 @@ namespace Umbraco.Web.UI.Controls // Create size changer sb.Append("
" + - "" + - "Small thumbnails" + - "" + - "Medium thumbnails" + - "" + - "Large thumbnails" + + "" + + "" + + "" + + "" + + "" + + "" + "
"); // Create the filter input - sb.Append("
Filter:
"); + sb.Append("
Filter:
"); // Create throbber to display whilst loading items sb.Append(""); @@ -137,7 +137,8 @@ namespace Umbraco.Web.UI.Controls Page.ClientScript.RegisterStartupScript(typeof(FolderBrowser), "RegisterFolderBrowsers", - string.Format("$(function () {{ $(\".umbFolderBrowser\").folderBrowser({{ umbracoPath : '{0}', basePath : '{1}' }}); }});", + string.Format("$(function () {{ $(\".umbFolderBrowser\").folderBrowser({{ umbracoPath : '{0}', basePath : '{1}' }}); " + + "$(\".umbFolderBrowser #filterTerm\").keypress(function(event) {{ return event.keyCode != 13; }});}});", IOHelper.ResolveUrl(SystemDirectories.Umbraco), IOHelper.ResolveUrl(SystemDirectories.Base)), true); diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 81f3e019d8..de200db55c 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -3,7 +3,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; +using System.Web; +using System.Web.Hosting; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Web.Routing; using umbraco.businesslogic; @@ -60,14 +63,38 @@ namespace Umbraco.Web ApplicationStarted(sender, e); } - protected virtual void Application_Error(object sender, EventArgs e) + /// + /// A method that can be overridden to invoke code when the application has an error. + /// + /// + /// + protected virtual void OnApplicationError(object sender, EventArgs e) { - + } - protected virtual void Application_End(object sender, EventArgs e) + protected void Application_Error(object sender, EventArgs e) { + OnApplicationError(sender, e); + } + /// + /// A method that can be overridden to invoke code when the application shuts down. + /// + /// + /// + protected virtual void OnApplicationEnd(object sender, EventArgs e) + { + + } + + protected void Application_End(object sender, EventArgs e) + { + if (SystemUtilities.GetCurrentTrustLevel() == AspNetHostingPermissionLevel.Unrestricted) + { + LogHelper.Info("Application shutdown. Reason: " + HostingEnvironment.ShutdownReason); + } + OnApplicationEnd(sender, e); } } } diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 5f84c387f6..8368563990 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -271,10 +271,11 @@ namespace Umbraco.Web if (maybeDoc && GlobalSettings.IsReservedPathOrUrl(lpath)) maybeDoc = false; - if (!maybeDoc) - { - LogHelper.Warn("Not a document"); - } + //NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :) + //if (!maybeDoc) + //{ + // LogHelper.Warn("Not a document"); + //} return maybeDoc; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs index e8aca83c1a..5d816ce07f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs @@ -33,6 +33,7 @@ namespace umbraco.cms.presentation.Trees { get { + instance.EnsureTreesRegistered(); return instance; } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs index d8336963d2..ac18b1775f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs @@ -194,12 +194,14 @@ namespace umbraco xNode.Source = GetTreeServiceUrl(t.Id); xNode.HasChildren = t.HasChildren; - if (Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc && Template.HasView(t)) + if (Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc && ViewHelper.ViewExists(t)) { xNode.Action = "javascript:openView(" + t.Id + ");"; xNode.Icon = "settingView.gif"; xNode.OpenIcon = "settingView.gif"; - }else{ + } + else + { xNode.Action = "javascript:openTemplate(" + t.Id + ");"; xNode.Icon = "settingTemplate.gif"; xNode.OpenIcon = "settingTemplate.gif"; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs index 5a9a9ae43c..dc57f13324 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs @@ -191,10 +191,10 @@ namespace umbraco.presentation.developer.packages else { if (e.CommandName == "save") - savePackage(true); + SavePackage(true); if (e.CommandName == "publish") { - savePackage(false); + SavePackage(false); int packageID = int.Parse(Request.QueryString["id"]); //string packFileName = cms.businesslogic.packager. Publish.publishPackage(packageID); @@ -216,7 +216,7 @@ namespace umbraco.presentation.developer.packages protected void generateXML(object sender, EventArgs e) { } - private void savePackage(bool showNotification) { + private void SavePackage(bool showNotification) { pack.Author = packageAuthorName.Text; pack.AuthorUrl = packageAuthorUrl.Text; @@ -345,7 +345,7 @@ namespace umbraco.presentation.developer.packages packageFilesRepeater.DataBind(); } - private static string joinList(List list, char seperator) { + private static string JoinList(List list, char seperator) { string retVal = ""; foreach (string str in list) { retVal += str + seperator.ToString(); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs index c64f125608..54a6ecb348 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs @@ -22,11 +22,10 @@ namespace umbraco.presentation.developer.packages /// public partial class Installer : BasePages.UmbracoEnsuredPage { - private Control configControl; - private cms.businesslogic.packager.repositories.Repository repo; - - private cms.businesslogic.packager.Installer p = new cms.businesslogic.packager.Installer(); - private string tempFileName = ""; + private Control _configControl; + private cms.businesslogic.packager.repositories.Repository _repo; + private readonly cms.businesslogic.packager.Installer _installer = new cms.businesslogic.packager.Installer(); + private string _tempFileName = ""; protected void Page_Load(object sender, System.EventArgs e) { @@ -47,34 +46,34 @@ namespace umbraco.presentation.developer.packages //if we are actually in the middle of installing something... if (!String.IsNullOrEmpty(helper.Request("installing"))) { - hideAllPanes(); + HideAllPanes(); pane_installing.Visible = true; - processInstall(helper.Request("installing")); + ProcessInstall(helper.Request("installing")); } else if (!String.IsNullOrEmpty(helper.Request("guid")) && !String.IsNullOrEmpty(helper.Request("repoGuid"))) { //we'll fetch the local information we have about our repo, to find out what webservice to query. - repo = cms.businesslogic.packager.repositories.Repository.getByGuid(helper.Request("repoGuid")); + _repo = cms.businesslogic.packager.repositories.Repository.getByGuid(helper.Request("repoGuid")); - if (repo.HasConnection()) + if (_repo.HasConnection()) { //from the webservice we'll fetch some info about the package. - cms.businesslogic.packager.repositories.Package pack = repo.Webservice.PackageByGuid(helper.Request("guid")); + cms.businesslogic.packager.repositories.Package pack = _repo.Webservice.PackageByGuid(helper.Request("guid")); //if the package is protected we will ask for the users credentials. (this happens every time they try to fetch anything) if (!pack.Protected) { //if it isn't then go straigt to the accept licens screen - tempFile.Value = p.Import(repo.fetch(helper.Request("guid"))); - updateSettings(); + tempFile.Value = _installer.Import(_repo.fetch(helper.Request("guid"))); + UpdateSettings(); } else if (!IsPostBack) { //Authenticate against the repo - hideAllPanes(); + HideAllPanes(); pane_authenticate.Visible = true; } @@ -83,7 +82,7 @@ namespace umbraco.presentation.developer.packages { fb.Style.Add("margin-top", "7px"); fb.type = global::umbraco.uicontrols.Feedback.feedbacktype.error; - fb.Text = "No connection to repository. Runway could not be installed as there was no connection to: '" + repo.RepositoryUrl + "'"; + fb.Text = "No connection to repository. Runway could not be installed as there was no connection to: '" + _repo.RepositoryUrl + "'"; pane_upload.Visible = false; } } @@ -99,11 +98,11 @@ namespace umbraco.presentation.developer.packages { try { - tempFileName = Guid.NewGuid().ToString() + ".umb"; - string fileName = SystemDirectories.Data + System.IO.Path.DirectorySeparatorChar + tempFileName; + _tempFileName = Guid.NewGuid().ToString() + ".umb"; + string fileName = SystemDirectories.Data + System.IO.Path.DirectorySeparatorChar + _tempFileName; file1.PostedFile.SaveAs(IOHelper.MapPath(fileName)); - tempFile.Value = p.Import(tempFileName); - updateSettings(); + tempFile.Value = _installer.Import(_tempFileName); + UpdateSettings(); } catch (Exception ex) { @@ -116,75 +115,75 @@ namespace umbraco.presentation.developer.packages protected void fetchProtectedPackage(object sender, EventArgs e) { //we auth against the webservice. This key will be used to fetch the protected package. - string memberGuid = repo.Webservice.authenticate(tb_email.Text, library.md5(tb_password.Text)); + string memberGuid = _repo.Webservice.authenticate(tb_email.Text, library.md5(tb_password.Text)); //if we auth correctly and get a valid key back, we will fetch the file from the repo webservice. if (!string.IsNullOrEmpty(memberGuid)) { - tempFile.Value = p.Import(repo.fetch(helper.Request("guid"), memberGuid)); - updateSettings(); + tempFile.Value = _installer.Import(_repo.fetch(helper.Request("guid"), memberGuid)); + UpdateSettings(); } } //this loads the accept license screen - private void updateSettings() + private void UpdateSettings() { - hideAllPanes(); + HideAllPanes(); pane_acceptLicense.Visible = true; - pane_acceptLicenseInner.Text = "Installing the package: " + p.Name; - Panel1.Text = "Installing the package: " + p.Name; + pane_acceptLicenseInner.Text = "Installing the package: " + _installer.Name; + Panel1.Text = "Installing the package: " + _installer.Name; - if (p.ContainsUnsecureFiles && repo == null) + if (_installer.ContainsUnsecureFiles && _repo == null) { pp_unsecureFiles.Visible = true; - foreach (string str in p.UnsecureFiles) + foreach (string str in _installer.UnsecureFiles) { lt_files.Text += "
  • " + str + "
  • "; } } - if (p.ContainsMacroConflict) + if (_installer.ContainsMacroConflict) { pp_macroConflicts.Visible = true; - foreach (var item in p.ConflictingMacroAliases) + foreach (var item in _installer.ConflictingMacroAliases) { ltrMacroAlias.Text += "
  • " + item.Key + " (Alias: " + item.Value + ")
  • "; } } - if (p.ContainsTemplateConflicts) + if (_installer.ContainsTemplateConflicts) { pp_templateConflicts.Visible = true; - foreach (var item in p.ConflictingTemplateAliases) + foreach (var item in _installer.ConflictingTemplateAliases) { ltrTemplateAlias.Text += "
  • " + item.Key + " (Alias: " + item.Value + ")
  • "; } } - if (p.ContainsStyleSheeConflicts) + if (_installer.ContainsStyleSheeConflicts) { pp_stylesheetConflicts.Visible = true; - foreach (var item in p.ConflictingStyleSheetNames) + foreach (var item in _installer.ConflictingStyleSheetNames) { ltrStylesheetNames.Text += "
  • " + item.Key + " (Alias: " + item.Value + ")
  • "; } } - LabelName.Text = p.Name + " Version: " + p.Version; - LabelMore.Text = "" + p.Url + ""; - LabelAuthor.Text = "" + p.Author + ""; - LabelLicense.Text = "" + p.License + ""; + LabelName.Text = _installer.Name + " Version: " + _installer.Version; + LabelMore.Text = "" + _installer.Url + ""; + LabelAuthor.Text = "" + _installer.Author + ""; + LabelLicense.Text = "" + _installer.License + ""; - if (p.ReadMe != "") - readme.Text = "
    " + library.ReplaceLineBreaks(library.StripHtml(p.ReadMe)) + "
    "; + if (_installer.ReadMe != "") + readme.Text = "
    " + library.ReplaceLineBreaks(library.StripHtml(_installer.ReadMe)) + "
    "; else readme.Text = "No information
    "; } - private void processInstall(string currentStep) + private void ProcessInstall(string currentStep) { string dir = helper.Request("dir"); int packageId = 0; @@ -193,20 +192,20 @@ namespace umbraco.presentation.developer.packages //first load in the config from the temporary directory //this will ensure that the installer have access to all the new files and the package manifest - p.LoadConfig(dir); + _installer.LoadConfig(dir); switch (currentStep) { case "businesslogic": - p.InstallBusinessLogic(packageId, dir); + _installer.InstallBusinessLogic(packageId, dir); //making sure that publishing actions performed from the cms layer gets pushed to the presentation library.RefreshContent(); - if (p.Control != null && p.Control != "") + if (_installer.Control != null && _installer.Control != "") { Response.Redirect("installer.aspx?installing=customInstaller&dir=" + dir + "&pId=" + packageId.ToString()); } @@ -216,26 +215,26 @@ namespace umbraco.presentation.developer.packages } break; case "customInstaller": - if (p.Control != null && p.Control != "") + if (_installer.Control != null && _installer.Control != "") { - hideAllPanes(); + HideAllPanes(); - configControl = new System.Web.UI.UserControl().LoadControl(SystemDirectories.Root + p.Control); - configControl.ID = "packagerConfigControl"; + _configControl = new System.Web.UI.UserControl().LoadControl(SystemDirectories.Root + _installer.Control); + _configControl.ID = "packagerConfigControl"; - pane_optional.Controls.Add(configControl); + pane_optional.Controls.Add(_configControl); pane_optional.Visible = true; } else { - hideAllPanes(); + HideAllPanes(); pane_success.Visible = true; BasePage.Current.ClientTools.ReloadActionNode(true, true); } break; case "finished": - hideAllPanes(); - string url = p.Url; + HideAllPanes(); + string url = _installer.Url; string packageViewUrl = "installedPackage.aspx?id=" + packageId.ToString(); bt_viewInstalledPackage.OnClientClick = "document.location = '" + packageViewUrl + "'; return false;"; @@ -247,7 +246,7 @@ namespace umbraco.presentation.developer.packages pane_success.Visible = true; BasePage.Current.ClientTools.ReloadActionNode(true, true); - p.InstallCleanUp(packageId, dir); + _installer.InstallCleanUp(packageId, dir); //clear the tree cache ClientTools.ClearClientTreeCache() @@ -269,30 +268,30 @@ namespace umbraco.presentation.developer.packages //we will now create the installer manifest, which means that umbraco can register everything that gets added to the system //this returns an id of the manifest. - p.LoadConfig(tempFile.Value); + _installer.LoadConfig(tempFile.Value); - int pId = p.CreateManifest(tempFile.Value, helper.Request("guid"), helper.Request("repoGuid")); + int pId = _installer.CreateManifest(tempFile.Value, helper.Request("guid"), helper.Request("repoGuid")); //and then copy over the files. This will take some time if it contains .dlls that will reboot the system.. - p.InstallFiles(pId, tempFile.Value); + _installer.InstallFiles(pId, tempFile.Value); Response.Redirect("installer.aspx?installing=businesslogic&dir=" + tempFile.Value + "&pId=" + pId.ToString()); } - private void drawConfig() + private void DrawConfig() { - hideAllPanes(); + HideAllPanes(); - configControl = new System.Web.UI.UserControl().LoadControl(SystemDirectories.Root + helper.Request("config")); - configControl.ID = "packagerConfigControl"; + _configControl = new System.Web.UI.UserControl().LoadControl(SystemDirectories.Root + helper.Request("config")); + _configControl.ID = "packagerConfigControl"; - pane_optional.Controls.Add(configControl); + pane_optional.Controls.Add(_configControl); pane_optional.Visible = true; } - private void hideAllPanes() + private void HideAllPanes() { pane_authenticate.Visible = false; pane_acceptLicense.Visible = false; diff --git a/src/umbraco.businesslogic/ApplicationRegistrar.cs b/src/umbraco.businesslogic/ApplicationRegistrar.cs index d29d50d90f..2432ad783c 100644 --- a/src/umbraco.businesslogic/ApplicationRegistrar.cs +++ b/src/umbraco.businesslogic/ApplicationRegistrar.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Data.SqlClient; +using System.Linq; using System.Xml.Linq; using Umbraco.Core; using umbraco.BusinessLogic.Utils; @@ -29,6 +31,11 @@ namespace umbraco.BusinessLogic public ApplicationRegistrar() { + + //don't do anything if the application is not configured! + if (!ApplicationContext.Current.IsConfigured) + return; + // Load all Applications by attribute and add them to the XML config var types = PluginManager.Current.ResolveApplications(); @@ -40,7 +47,7 @@ namespace umbraco.BusinessLogic var allAliases = Application.getAll().Select(x => x.alias).Concat(attrs.Select(x => x.Alias)); var inString = "'" + string.Join("','", allAliases) + "'"; - + Application.LoadXml(doc => { foreach (var attr in attrs) @@ -51,16 +58,17 @@ namespace umbraco.BusinessLogic new XAttribute("icon", attr.Icon), new XAttribute("sortOrder", attr.SortOrder))); } - - var dbApps = SqlHelper.ExecuteReader("SELECT * FROM umbracoApp WHERE appAlias NOT IN ("+ inString +")"); - while (dbApps.Read()) - { - doc.Root.Add(new XElement("add", - new XAttribute("alias", dbApps.GetString("appAlias")), - new XAttribute("name", dbApps.GetString("appName")), - new XAttribute("icon", dbApps.GetString("appIcon")), - new XAttribute("sortOrder", dbApps.GetByte("sortOrder")))); - } + + var dbApps = SqlHelper.ExecuteReader("SELECT * FROM umbracoApp WHERE appAlias NOT IN (" + inString + ")"); + while (dbApps.Read()) + { + doc.Root.Add(new XElement("add", + new XAttribute("alias", dbApps.GetString("appAlias")), + new XAttribute("name", dbApps.GetString("appName")), + new XAttribute("icon", dbApps.GetString("appIcon")), + new XAttribute("sortOrder", dbApps.GetByte("sortOrder")))); + } + }, true); diff --git a/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs b/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs index 70f709c84f..97628bbacd 100644 --- a/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs +++ b/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs @@ -30,6 +30,10 @@ namespace umbraco.BusinessLogic public ApplicationTreeRegistrar() { + //don't do anything if the application is not configured! + if (!ApplicationContext.Current.IsConfigured) + return; + // Load all Trees by attribute and add them to the XML config var types = PluginManager.Current.ResolveAttributedTrees(); diff --git a/src/umbraco.businesslogic/IO/IOHelper.cs b/src/umbraco.businesslogic/IO/IOHelper.cs index eb99389fcc..8a7e10d9eb 100644 --- a/src/umbraco.businesslogic/IO/IOHelper.cs +++ b/src/umbraco.businesslogic/IO/IOHelper.cs @@ -53,12 +53,13 @@ namespace umbraco.IO } //use a tilde character instead of the complete path + [Obsolete("This method is no longer in use and will be removed in future versions")] public static string returnPath(string settingsKey, string standardPath, bool useTilde) { return Umbraco.Core.IO.IOHelper.ReturnPath(settingsKey, standardPath, useTilde); } - + [Obsolete("This method is no longer in use and will be removed in future versions")] public static string returnPath(string settingsKey, string standardPath) { return Umbraco.Core.IO.IOHelper.ReturnPath(settingsKey, standardPath); diff --git a/src/umbraco.cms/businesslogic/Content.cs b/src/umbraco.cms/businesslogic/Content.cs index 05b22daec3..90b4c35caa 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -508,8 +508,13 @@ namespace umbraco.cms.businesslogic /// /// /// The new version Id - protected Guid createNewVersion(DateTime versionDate) + protected Guid createNewVersion(DateTime versionDate = default(DateTime)) { + if (versionDate == default (DateTime)) + { + versionDate = DateTime.Now; + } + ClearLoadedProperties(); Guid newVersion = Guid.NewGuid(); diff --git a/src/umbraco.cms/businesslogic/ContentType.cs b/src/umbraco.cms/businesslogic/ContentType.cs index 6534972360..8367e8335b 100644 --- a/src/umbraco.cms/businesslogic/ContentType.cs +++ b/src/umbraco.cms/businesslogic/ContentType.cs @@ -658,7 +658,7 @@ namespace umbraco.cms.businesslogic SqlHelper.CreateParameter("@parentContentTypeId", this.Id)) > 0; } - public List GetChildTypes() + public IEnumerable GetChildTypes() { var cts = new List(); using (IRecordsReader dr = @@ -694,7 +694,7 @@ namespace umbraco.cms.businesslogic // has a nodetype of this id var contentTypeToRemove = new ContentType(parentContentTypeId); - removeMasterPropertyTypeData(contentTypeToRemove, this); + RemoveMasterPropertyTypeData(contentTypeToRemove, this); SqlHelper.ExecuteNonQuery( "DELETE FROM [cmsContentType2ContentType] WHERE parentContentTypeId = @parentContentTypeId AND childContentTypeId = @childContentTypeId", @@ -704,7 +704,7 @@ namespace umbraco.cms.businesslogic } } - private void removeMasterPropertyTypeData(ContentType contentTypeToRemove, ContentType currentContentType) + private void RemoveMasterPropertyTypeData(ContentType contentTypeToRemove, ContentType currentContentType) { foreach (var pt in contentTypeToRemove.PropertyTypes) { @@ -723,10 +723,10 @@ namespace umbraco.cms.businesslogic } // remove sub data too foreach(var ct in currentContentType.GetChildTypes()) - removeMasterPropertyTypeData(contentTypeToRemove, ct); + RemoveMasterPropertyTypeData(contentTypeToRemove, ct); } - public List PropertyTypeGroups + public IEnumerable PropertyTypeGroups { get { return PropertyTypeGroup.GetPropertyTypeGroupsFromContentType(Id); } } @@ -1170,7 +1170,7 @@ namespace umbraco.cms.businesslogic private void InitializeVirtualTabs() { m_VirtualTabs = new List(); - foreach (PropertyTypeGroup ptg in PropertyTypeGroups.FindAll(x => x.ParentId == 0 && x.ContentTypeId == this.Id)) + foreach (PropertyTypeGroup ptg in PropertyTypeGroups.Where(x => x.ParentId == 0 && x.ContentTypeId == this.Id)) m_VirtualTabs.Add(new Tab(ptg.Id, ptg.Name, ptg.SortOrder, this)); // Master Content Type @@ -1335,19 +1335,24 @@ namespace umbraco.cms.businesslogic { // NH, temp fix for 4.9 to use the new PropertyTypeGroup API - List pts = PropertyTypeGroup.GetPropertyTypeGroup(this.Id).GetPropertyTypes(); + var pts = PropertyTypeGroup.GetPropertyTypeGroup(this.Id).GetPropertyTypes(); if (includeInheritedProperties) { // we need to cms.businesslogic.ContentType ct = cms.businesslogic.ContentType.GetContentType(contentTypeId); return - pts.FindAll( + pts.Where( x => ct.MasterContentTypes.Contains( x.ContentTypeId) || x.ContentTypeId == contentTypeId).ToArray(); } - return pts.FindAll(x => x.ContentTypeId == contentTypeId).ToArray(); + return pts.Where(x => x.ContentTypeId == contentTypeId).ToArray(); + + //TODO: Why is this code still here?? revision: 1bd01ec17bf9 + // shouldn't this code be removed/commented out or something????? + // the below code is completely unreachable. + // zb-00040 #29889 : fix cache key issues! // now maintaining a cache of local properties per contentTypeId, then merging when required @@ -1388,7 +1393,7 @@ namespace umbraco.cms.businesslogic // while (dr.Read()) // tmp1.Add(PropertyType.GetPropertyType(dr.GetInt("id"))); // } - return tmp1; + return tmp1.ToList(); }); tmp.AddRange(ptypes); diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index 8b51588f54..b55ea4715d 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -332,7 +332,10 @@ namespace umbraco.cms.businesslogic.packager if (saveNeeded) { insPack.Save(); saveNeeded = false; } - // Add master templates + //NOTE: SD: I'm pretty sure the only thing the below script does is ensure that the Master template Id is set + // in the database, but this is also duplicating the saving of the design content since the above Template.Import + // already does this. I've left this for now because I'm not sure the reprocussions of removing it but seems there + // is a lot of excess database calls happening here. foreach (XmlNode n in _packageConfig.DocumentElement.SelectNodes("Templates/Template")) { string master = xmlHelper.GetNodeValue(n.SelectSingleNode("Master")); template.Template t = template.Template.GetByAlias(xmlHelper.GetNodeValue(n.SelectSingleNode("Alias"))); diff --git a/src/umbraco.cms/businesslogic/Packager/data.cs b/src/umbraco.cms/businesslogic/Packager/data.cs index 8e0c5ea366..9c0241c31f 100644 --- a/src/umbraco.cms/businesslogic/Packager/data.cs +++ b/src/umbraco.cms/businesslogic/Packager/data.cs @@ -3,6 +3,7 @@ using System.Xml; using System.Xml.XPath; using System.Collections.Generic; using System.IO; +using Umbraco.Core; namespace umbraco.cms.businesslogic.packager { @@ -46,6 +47,24 @@ namespace umbraco.cms.businesslogic.packager { _source = new XmlDocument(); } + + //error checking here + if (File.Exists(dataSource)) + { + var isEmpty = false; + using (var sr = new StreamReader(dataSource)) + { + if (sr.ReadToEnd().IsNullOrWhiteSpace()) + { + isEmpty = true; + } + } + if (isEmpty) + { + File.WriteAllText(dataSource, @""); + } + } + _source.Load(dataSource); } diff --git a/src/umbraco.cms/businesslogic/propertytype/PropertyTypeGroup.cs b/src/umbraco.cms/businesslogic/propertytype/PropertyTypeGroup.cs index c2a3c138fe..5f322abdef 100644 --- a/src/umbraco.cms/businesslogic/propertytype/PropertyTypeGroup.cs +++ b/src/umbraco.cms/businesslogic/propertytype/PropertyTypeGroup.cs @@ -37,20 +37,20 @@ namespace umbraco.cms.businesslogic.propertytype SortOrder = -1; // we set this to -1 so in the save method we can get the current highest sortorder in case it's not sat after init (ie. if you want to force a sortOrder) } - public List GetPropertyTypes(List contentTypeIds) + public IEnumerable GetPropertyTypes(List contentTypeIds) { return PropertyType.GetPropertyTypesByGroup(Id, contentTypeIds); } - public List GetPropertyTypes() + public IEnumerable GetPropertyTypes() { return PropertyType.GetPropertyTypesByGroup(Id); } //TODO: Verify support for master doctypes / mixins! - public List GetPropertyTypeGroups() + public IEnumerable GetPropertyTypeGroups() { - List ptgs = new List(); + var ptgs = new List(); using (IRecordsReader dr = SqlHelper.ExecuteReader(@" SELECT @@ -86,7 +86,7 @@ namespace umbraco.cms.businesslogic.propertytype id = @id ", SqlHelper.CreateParameter("@id", Id), - SqlHelper.CreateParameter("@parentGroupId", convertParentId(ParentId)), + SqlHelper.CreateParameter("@parentGroupId", ConvertParentId(ParentId)), SqlHelper.CreateParameter("@contentTypeId", ContentTypeId), SqlHelper.CreateParameter("@sortOrder", SortOrder), SqlHelper.CreateParameter("@name", Name) @@ -106,7 +106,7 @@ namespace umbraco.cms.businesslogic.propertytype VALUES (@parentGroupId, @contentTypeId, @sortOrder, @name) ", - SqlHelper.CreateParameter("@parentGroupId", convertParentId(ParentId)), + SqlHelper.CreateParameter("@parentGroupId", ConvertParentId(ParentId)), SqlHelper.CreateParameter("@contentTypeId", ContentTypeId), SqlHelper.CreateParameter("@sortOrder", SortOrder), SqlHelper.CreateParameter("@name", Name) @@ -133,7 +133,7 @@ namespace umbraco.cms.businesslogic.propertytype SqlHelper.CreateParameter("@id", Id)); } - public void Load() + internal void Load() { using (IRecordsReader dr = SqlHelper.ExecuteReader(@" @@ -164,9 +164,9 @@ namespace umbraco.cms.businesslogic.propertytype return ptg; } - public static List GetPropertyTypeGroupsFromContentType(int contentTypeId) + public static IEnumerable GetPropertyTypeGroupsFromContentType(int contentTypeId) { - List ptgs = new List(); + var ptgs = new List(); using (IRecordsReader dr = SqlHelper.ExecuteReader(@" SELECT @@ -185,7 +185,7 @@ namespace umbraco.cms.businesslogic.propertytype return ptgs; } - private object convertParentId(int parentId) + private object ConvertParentId(int parentId) { if (parentId == 0) return DBNull.Value; diff --git a/src/umbraco.cms/businesslogic/propertytype/propertytype.cs b/src/umbraco.cms/businesslogic/propertytype/propertytype.cs index 365529f8ab..8d3fcd09c7 100644 --- a/src/umbraco.cms/businesslogic/propertytype/propertytype.cs +++ b/src/umbraco.cms/businesslogic/propertytype/propertytype.cs @@ -311,11 +311,11 @@ namespace umbraco.cms.businesslogic.propertytype public static PropertyType[] GetAll() { - List result = GetPropertyTypes(); + var result = GetPropertyTypes(); return result.ToArray(); } - public static List GetPropertyTypes() + public static IEnumerable GetPropertyTypes() { var result = new List(); using (IRecordsReader dr = @@ -331,12 +331,12 @@ namespace umbraco.cms.businesslogic.propertytype return result; } - public static List GetPropertyTypesByGroup(int groupId, List contentTypeIds) + public static IEnumerable GetPropertyTypesByGroup(int groupId, List contentTypeIds) { - return GetPropertyTypesByGroup(groupId).FindAll(x => contentTypeIds.Contains(x.ContentTypeId)); + return GetPropertyTypesByGroup(groupId).Where(x => contentTypeIds.Contains(x.ContentTypeId)); } - public static List GetPropertyTypesByGroup(int groupId) + public static IEnumerable GetPropertyTypesByGroup(int groupId) { var result = new List(); using (IRecordsReader dr = @@ -382,11 +382,11 @@ namespace umbraco.cms.businesslogic.propertytype FlushCache(); // clean all properties on inherited document types (if this propertytype is removed from a master) - cleanPropertiesOnDeletion(_contenttypeid); + CleanPropertiesOnDeletion(_contenttypeid); // DocumentType.GetAllAsList().FindAll(dt => dt.MasterContentType == _contenttypeid).ForEach(dt => cleanPropertiesOnDeletion(dt.Id)); // Delete all properties of propertytype - cleanPropertiesOnDeletion(_contenttypeid); + CleanPropertiesOnDeletion(_contenttypeid); // Delete PropertyType .. SqlHelper.ExecuteNonQuery("Delete from cmsPropertyType where id = " + Id); @@ -409,12 +409,12 @@ namespace umbraco.cms.businesslogic.propertytype } } - private void cleanPropertiesOnDeletion(int contentTypeId) + private void CleanPropertiesOnDeletion(int contentTypeId) { // first delete from all master document types //TODO: Verify no endless loops with mixins DocumentType.GetAllAsList().FindAll(dt => dt.MasterContentTypes.Contains(contentTypeId)).ForEach( - dt => cleanPropertiesOnDeletion(dt.Id)); + dt => CleanPropertiesOnDeletion(dt.Id)); // then remove from the current doc type Content[] objs = Content.getContentOfContentType(new ContentType(contentTypeId)); diff --git a/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs b/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs index 700d8b4165..33375dda77 100644 --- a/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs +++ b/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs @@ -5,22 +5,34 @@ using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using Umbraco.Core; using Umbraco.Core.IO; namespace umbraco.cms.businesslogic.template { - internal class MasterpageHelper + internal class MasterPageHelper { internal static readonly string DefaultMasterTemplate = SystemDirectories.Umbraco + "/masterpages/default.master"; - internal static string CreateMasterpageFile(Template t, bool overWrite = false) + + internal static bool MasterPageExists(Template t) + { + return File.Exists(GetFilePath(t)); + } + + internal static string GetFilePath(Template t) + { + return IOHelper.MapPath(SystemDirectories.Masterpages + "/" + t.Alias.Replace(" ", "") + ".master"); + } + + internal static string CreateMasterPage(Template t, bool overWrite = false) { string masterpageContent = ""; - if (!File.Exists(t.MasterPageFile) || overWrite) + if (!File.Exists(GetFilePath(t)) || overWrite) masterpageContent = SaveTemplateToFile(t, t.Alias); else { - System.IO.TextReader tr = new StreamReader(t.MasterPageFile); + System.IO.TextReader tr = new StreamReader(GetFilePath(t)); masterpageContent = tr.ReadToEnd(); tr.Close(); } @@ -28,11 +40,12 @@ namespace umbraco.cms.businesslogic.template return masterpageContent; } - internal static string GetMasterpageFile(Template t) + internal static string GetFileContents(Template t) { string masterpageContent = ""; - if (File.Exists(t.MasterPageFile)){ - System.IO.TextReader tr = new StreamReader(t.MasterPageFile); + if (File.Exists(GetFilePath(t))) + { + System.IO.TextReader tr = new StreamReader(GetFilePath(t)); masterpageContent = tr.ReadToEnd(); tr.Close(); } @@ -40,7 +53,7 @@ namespace umbraco.cms.businesslogic.template return masterpageContent; } - internal static string UpdateMasterpageFile(Template t, string currentAlias) + internal static string UpdateMasterPageFile(Template t, string currentAlias) { return SaveTemplateToFile(t, currentAlias); } @@ -98,10 +111,9 @@ namespace umbraco.cms.businesslogic.template //Ensure that child templates have the right master masterpage file name if (template.HasChildren) { - //store children array here because iterating over an Array property object is very inneficient. var c = template.Children; foreach (CMSNode cmn in c) - UpdateMasterpageFile(new Template(cmn.Id), null); + UpdateMasterPageFile(new Template(cmn.Id), null); } //then kill the old file.. @@ -111,7 +123,7 @@ namespace umbraco.cms.businesslogic.template } // save the file in UTF-8 - System.IO.File.WriteAllText(template.MasterPageFile, masterPageContent, System.Text.Encoding.UTF8); + System.IO.File.WriteAllText(GetFilePath(template), masterPageContent, System.Text.Encoding.UTF8); return masterPageContent; } @@ -131,9 +143,10 @@ namespace umbraco.cms.businesslogic.template return masterPageContent; } - private static bool IsMasterPageSyntax(string code) + internal static bool IsMasterPageSyntax(string code) { - return code.Contains("<%@ Master") || code.Contains(" + /// Returns the file path for the current template + /// + public string TemplateFilePath + { + get + { + switch (DetermineRenderingEngine(this)) + { + case RenderingEngine.Mvc: + return ViewHelper.GetFilePath(this); + case RenderingEngine.WebForms: + return MasterPageHelper.GetFilePath(this); + default: + throw new ArgumentOutOfRangeException(); + } + } + } + public static Hashtable TemplateAliases { get { return _templateAliases; } @@ -134,18 +154,13 @@ namespace umbraco.cms.businesslogic.template } dr.Close(); - if (Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc && Template.HasView(this)) - _design = ViewHelper.GetViewFile(this); + if (Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc && ViewHelper.ViewExists(this)) + _design = ViewHelper.GetFileContents(this); else - _design = MasterpageHelper.GetMasterpageFile(this); + _design = MasterPageHelper.GetFileContents(this); } - - private bool isMasterPageSyntax(string code) - { - return code.Contains("<%@ Master") || code.Contains(" x.allowedTemplates.Select(t => t.Id).Contains(this.Id)); } - public static Template MakeNew(string Name, BusinessLogic.User u, Template master) - { - Template t = MakeNew(Name, u); - t.MasterTemplate = master.Id; + /// + /// This checks what the default rendering engine is set in config but then also ensures that there isn't already + /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate + /// rendering engine to use. + /// + /// + /// If a template body is specified we'll check if it contains master page markup, if it does we'll auto assume its webforms + /// + /// + /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx + /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml + /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. + /// This is mostly related to installing packages since packages install file templates to the file system and then create the + /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. + /// + private static RenderingEngine DetermineRenderingEngine(Template t, string design = null) + { + var engine = Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine; - if (Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc) - ViewHelper.CreateViewFile(t, true); - else - MasterpageHelper.CreateMasterpageFile(t, true); - - + if (!design.IsNullOrWhiteSpace() && MasterPageHelper.IsMasterPageSyntax(design)) + { + //there is a design but its definitely a webforms design + return RenderingEngine.WebForms; + } + + switch (engine) + { + case RenderingEngine.Mvc: + //check if there's a view in ~/masterpages + if (MasterPageHelper.MasterPageExists(t) && !ViewHelper.ViewExists(t)) + { + //change this to webforms since there's already a file there for this template alias + engine = RenderingEngine.WebForms; + } + break; + case RenderingEngine.WebForms: + //check if there's a view in ~/views + if (ViewHelper.ViewExists(t) && !MasterPageHelper.MasterPageExists(t)) + { + //change this to mvc since there's already a file there for this template alias + engine = RenderingEngine.Mvc; + } + break; + } + return engine; + } + + public static Template MakeNew(string Name, BusinessLogic.User u, Template master, string design = null) + { + Template t = MakeNew(Name, u, design); + t.MasterTemplate = master.Id; /* if (UmbracoSettings.UseAspNetMasterPages) @@ -346,7 +401,7 @@ namespace umbraco.cms.businesslogic.template return t; } - public static Template MakeNew(string name, BusinessLogic.User u) + public static Template MakeNew(string name, BusinessLogic.User u, string design = null) { // CMSNode MakeNew(int parentId, Guid objectType, int userId, int level, string text, Guid uniqueID) @@ -374,11 +429,21 @@ namespace umbraco.cms.businesslogic.template NewEventArgs e = new NewEventArgs(); t.OnNew(e); - if (Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc) - t._design = ViewHelper.CreateViewFile(t); - else - t._design = MasterpageHelper.CreateMasterpageFile(t); + switch (DetermineRenderingEngine(t, design)) + { + case RenderingEngine.Mvc: + ViewHelper.CreateViewFile(t, true); + break; + case RenderingEngine.WebForms: + MasterPageHelper.CreateMasterPage(t, true); + break; + } + //if a design is supplied ensure it is updated. + if (!design.IsNullOrWhiteSpace()) + { + t.ImportDesign(design); + } return t; } @@ -506,8 +571,8 @@ namespace umbraco.cms.businesslogic.template if (System.IO.File.Exists(MasterPageFile)) System.IO.File.Delete(MasterPageFile); - if (System.IO.File.Exists(Umbraco.Core.IO.IOHelper.MapPath(ViewHelper.ViewPath(this)))) - System.IO.File.Delete(Umbraco.Core.IO.IOHelper.MapPath(ViewHelper.ViewPath(this))); + if (System.IO.File.Exists(Umbraco.Core.IO.IOHelper.MapPath(ViewHelper.ViewPath(this.Alias)))) + System.IO.File.Delete(Umbraco.Core.IO.IOHelper.MapPath(ViewHelper.ViewPath(this.Alias))); FireAfterDelete(e); } @@ -578,17 +643,17 @@ namespace umbraco.cms.businesslogic.template public string EnsureMasterPageSyntax(string masterPageContent) { - replaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", true); - replaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", false); + ReplaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", true); + ReplaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", false); // Parse the design for macros - replaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", true); - replaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", false); + ReplaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", true); + ReplaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", false); // Parse the design for load childs - masterPageContent = masterPageContent.Replace("", getAspNetMasterPageContentContainer()).Replace("", getAspNetMasterPageContentContainer()); + masterPageContent = masterPageContent.Replace("", GetAspNetMasterPageContentContainer()).Replace("", GetAspNetMasterPageContentContainer()); // Parse the design for aspnet forms - getAspNetMasterPageForm(ref masterPageContent); + GetAspNetMasterPageForm(ref masterPageContent); masterPageContent = masterPageContent.Replace("", ""); // Parse the design for aspnet heads masterPageContent = masterPageContent.Replace("", String.Format("", Alias.Replace(" ", ""))); @@ -683,15 +748,9 @@ namespace umbraco.cms.businesslogic.template File.WriteAllText(MasterPageFile, masterPageContent, System.Text.Encoding.UTF8); * */ - } + } - private string getMasterPageHeader() - { - return String.Format("<%@ Master Language=\"C#\" MasterPageFile=\"{0}\" AutoEventWireup=\"true\" %>", - currentMasterTemplateFileName()) + Environment.NewLine; - } - - private string currentMasterTemplateFileName() + private string CurrentMasterTemplateFileName() { if (MasterTemplate != 0) return SystemDirectories.Masterpages + "/" + new Template(MasterTemplate).Alias.Replace(" ", "") + ".master"; @@ -699,9 +758,9 @@ namespace umbraco.cms.businesslogic.template return UmbracoMasterTemplate; } - private void getAspNetMasterPageForm(ref string design) + private void GetAspNetMasterPageForm(ref string design) { - Match formElement = Regex.Match(design, getElementRegExp("?ASPNET_FORM", false), RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + Match formElement = Regex.Match(design, GetElementRegExp("?ASPNET_FORM", false), RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); if (formElement != null && formElement.Value != "") { string formReplace = String.Format("
    ", Alias.Replace(" ", "")); @@ -713,17 +772,17 @@ namespace umbraco.cms.businesslogic.template } } - private string getAspNetMasterPageContentContainer() + private string GetAspNetMasterPageContentContainer() { return String.Format( "", Alias.Replace(" ", "")); } - private void replaceElement(ref string design, string elementName, string newElementName, bool checkForQuotes) + private void ReplaceElement(ref string design, string elementName, string newElementName, bool checkForQuotes) { MatchCollection m = - Regex.Matches(design, getElementRegExp(elementName, checkForQuotes), + Regex.Matches(design, GetElementRegExp(elementName, checkForQuotes), RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); foreach (Match match in m) @@ -762,7 +821,7 @@ namespace umbraco.cms.businesslogic.template - private string getElementRegExp(string elementName, bool checkForQuotes) + private string GetElementRegExp(string elementName, bool checkForQuotes) { if (checkForQuotes) return String.Format("\"<[^>\\s]*\\b{0}(\\b[^>]*)>\"", elementName); @@ -811,24 +870,20 @@ namespace umbraco.cms.businesslogic.template string alias = xmlHelper.GetNodeValue(n.SelectSingleNode("Alias")); Template t = Template.GetByAlias(alias); + var design = xmlHelper.GetNodeValue(n.SelectSingleNode("Design")); if (t == null) { - t = MakeNew(xmlHelper.GetNodeValue(n.SelectSingleNode("Name")), u); + //create the template with the design if one is specified + t = MakeNew(xmlHelper.GetNodeValue(n.SelectSingleNode("Name")), u, + design.IsNullOrWhiteSpace() ? null : design); } t.Alias = alias; - t.ImportDesign(xmlHelper.GetNodeValue(n.SelectSingleNode("Design"))); return t; } - - public static bool HasView(Template t) - { - var path = Umbraco.Core.IO.SystemDirectories.MvcViews + "/" + t.Alias.Replace(" ", "") + ".cshtml"; - return System.IO.File.Exists(Umbraco.Core.IO.IOHelper.MapPath(path)); - } - + #region Events //EVENTS diff --git a/src/umbraco.cms/businesslogic/template/ViewHelper.cs b/src/umbraco.cms/businesslogic/template/ViewHelper.cs index c915e18d9f..82c554921b 100644 --- a/src/umbraco.cms/businesslogic/template/ViewHelper.cs +++ b/src/umbraco.cms/businesslogic/template/ViewHelper.cs @@ -9,10 +9,21 @@ namespace umbraco.cms.businesslogic.template { class ViewHelper { - internal static string GetViewFile(Template t) + internal static bool ViewExists(Template t) + { + string path = GetFilePath(t); + return File.Exists(path); + } + + internal static string GetFilePath(Template t) + { + return IOHelper.MapPath(ViewPath(t.Alias)); + } + + internal static string GetFileContents(Template t) { string viewContent = ""; - string path = IOHelper.MapPath(ViewPath(t)); + string path = IOHelper.MapPath(ViewPath(t.Alias)); if (File.Exists(path)) { @@ -27,7 +38,7 @@ namespace umbraco.cms.businesslogic.template internal static string CreateViewFile(Template t, bool overWrite = false) { string viewContent = ""; - string path = IOHelper.MapPath(ViewPath(t)); + string path = IOHelper.MapPath(ViewPath(t.Alias)); if (!File.Exists(path) || overWrite) viewContent = SaveTemplateToFile(t, t.Alias); @@ -44,21 +55,40 @@ namespace umbraco.cms.businesslogic.template internal static string SaveTemplateToFile(Template template, string currentAlias) { var design = EnsureInheritedLayout(template); - System.IO.File.WriteAllText(IOHelper.MapPath(ViewPath(template)), design, Encoding.UTF8); + System.IO.File.WriteAllText(IOHelper.MapPath(ViewPath(template.Alias)), design, Encoding.UTF8); return template.Design; } - internal static string UpdateViewFile(Template t) + internal static string UpdateViewFile(Template t, string currentAlias = null) { - var path = IOHelper.MapPath(ViewPath(t)); + var path = IOHelper.MapPath(ViewPath(t.Alias)); + + if (!string.IsNullOrEmpty(currentAlias) && currentAlias != t.Alias) + { + //NOTE: I don't think this is needed for MVC, this was ported over from the + // masterpages helper but I think only relates to when templates are stored in the db. + ////Ensure that child templates have the right master masterpage file name + //if (t.HasChildren) + //{ + // var c = t.Children; + // foreach (CMSNode cmn in c) + // UpdateViewFile(new Template(cmn.Id), null); + //} + + //then kill the old file.. + var oldFile = IOHelper.MapPath(ViewPath(currentAlias)); + if (System.IO.File.Exists(oldFile)) + System.IO.File.Delete(oldFile); + } + System.IO.File.WriteAllText(path, t.Design, Encoding.UTF8); return t.Design; } - public static string ViewPath(Template t) + public static string ViewPath(string alias) { - return Umbraco.Core.IO.SystemDirectories.MvcViews + "/" + t.Alias.Replace(" ", "") + ".cshtml"; + return Umbraco.Core.IO.SystemDirectories.MvcViews + "/" + alias.Replace(" ", "") + ".cshtml"; } diff --git a/src/umbraco.controls/CodeArea.cs b/src/umbraco.controls/CodeArea.cs index 7d3c9e1392..ba446b0cea 100644 --- a/src/umbraco.controls/CodeArea.cs +++ b/src/umbraco.controls/CodeArea.cs @@ -171,17 +171,17 @@ namespace umbraco.uicontrols } jsEventCode += @" - - //create the editor - var UmbEditor = new Umbraco.Controls.CodeEditor.UmbracoEditor(" + (!CodeMirrorEnabled).ToString().ToLower() + @", '" + this.ClientID + @"'); - var m_textEditor = jQuery('#" + this.ClientID + @"'); + $(function () { + //create the editor + var UmbEditor = new Umbraco.Controls.CodeEditor.UmbracoEditor(" + (!CodeMirrorEnabled).ToString().ToLower() + @", '" + this.ClientID + @"'); + var m_textEditor = jQuery('#" + this.ClientID + @"'); - //with codemirror adding divs for line numbers, we need to target a different element - m_textEditor = m_textEditor.find('iframe').length > 0 ? m_textEditor.children('div').get(0) : m_textEditor.get(0); + //with codemirror adding divs for line numbers, we need to target a different element + m_textEditor = m_textEditor.find('iframe').length > 0 ? m_textEditor.children('div').get(0) : m_textEditor.get(0); - jQuery(window).resize(function(){ resizeTextArea(m_textEditor, " + OffSetX.ToString() + "," + OffSetY.ToString() + @"); }); - jQuery(document).ready(function(){ resizeTextArea(m_textEditor, " + OffSetX.ToString() + "," + OffSetY.ToString() + @"); });"; - + jQuery(window).resize(function(){ resizeTextArea(m_textEditor, " + OffSetX.ToString() + "," + OffSetY.ToString() + @"); }); + jQuery(document).ready(function(){ resizeTextArea(m_textEditor, " + OffSetX.ToString() + "," + OffSetY.ToString() + @"); }); + });"; /* if (!UmbracoSettings.ScriptDisableEditor && HttpContext.Current.Request.Browser.Browser == "IE") { diff --git a/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs b/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs index a36b93e5f9..6e0a04320b 100644 --- a/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs +++ b/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Web; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Xml.Linq; using ClientDependency.Core; +using Umbraco.Core; using umbraco.cms.presentation.Trees; using umbraco.controls.Images; using umbraco.controls.Tree; @@ -17,679 +19,665 @@ using umbraco.IO; namespace umbraco.editorControls.MultiNodeTreePicker { - /// - /// The user interface to display to the content editor - /// - [ClientDependency(ClientDependencyType.Javascript, "ui/jqueryui.js", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "ui/jquery.tooltip.min.js", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "controls/Images/ImageViewer.js", "UmbracoRoot")] - public class MNTP_DataEditor : Control, INamingContainer - { - #region Static Constructor - - /// - /// This adds our filtered tree definition to the TreeDefinitionCollection at runtime - /// instead of having to declare it in the database - /// - static MNTP_DataEditor() - { - if (TreeDefinitionCollection.Instance - .Where(x => x.TreeType == typeof(FilteredContentTree)) - .Count() == 0) - { - lock (m_Locker) - { - //double check lock.... - - if (TreeDefinitionCollection.Instance - .Where(x => x.TreeType == typeof(FilteredContentTree)) - .Count() == 0) - { - //need to add our tree definitions to the collection. - - //find the content tree to duplicate - var contentTree = TreeDefinitionCollection.Instance.Where(x => x.Tree.Alias.ToUpper() == "CONTENT").Single(); - var filteredContentTree = new TreeDefinition(typeof(FilteredContentTree), - new umbraco.BusinessLogic.ApplicationTree(true, false, 0, - contentTree.Tree.ApplicationAlias, - "FilteredContentTree", - contentTree.Tree.Title, - contentTree.Tree.IconClosed, - contentTree.Tree.IconOpened, - "umbraco.editorControls", - "MultiNodeTreePicker.FilteredContentTree", - contentTree.Tree.Action), - contentTree.App); - - //find the media tree to duplicate - var mediaTree = TreeDefinitionCollection.Instance.Where(x => x.Tree.Alias.ToUpper() == "MEDIA").Single(); - var filteredMediaTree = new TreeDefinition(typeof(FilteredMediaTree), - new umbraco.BusinessLogic.ApplicationTree(true, false, 0, - mediaTree.Tree.ApplicationAlias, - "FilteredMediaTree", - contentTree.Tree.Title, - contentTree.Tree.IconClosed, - contentTree.Tree.IconOpened, - "umbraco.editorControls", - "MultiNodeTreePicker.FilteredMediaTree", - contentTree.Tree.Action), - contentTree.App); - - //add it to the collection at runtime - TreeDefinitionCollection.Instance.Add(filteredContentTree); - TreeDefinitionCollection.Instance.Add(filteredMediaTree); - } - } - } - } - - #endregion - - /// - /// Initializes a new instance of the class. - /// - public MNTP_DataEditor() - { - this.MediaTypesWithThumbnails = new string[] { "image" }; - ShowThumbnailsForMedia = true; - TreeToRender = "content"; - MaxNodeCount = -1; - MinNodeCount = 0; - StartNodeId = uQuery.RootNodeId; - ShowToolTips = true; - ControlHeight = 200; - } - - #region Static members - /// - /// Used for locking code blocks - /// - private static readonly object m_Locker = new object(); - #endregion - - #region Protected members - - /// - /// - /// - protected CustomValidator MinItemsValidator; - - /// - /// - /// - protected CustomTreeControl TreePickerControl; - - /// - /// - /// - protected Repeater SelectedValues; - - /// - /// - /// - protected HiddenField PickedValue; - - /// - /// - /// - protected HtmlGenericControl RightColumn; - #endregion - - #region public Properties - - /// - /// gets/sets the value based on an array of IDs selected - /// - public string[] SelectedIds - { - get - { - List val = new List(); - var splitVals = PickedValue.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - //this will make sure only the node count specified is saved - //into umbraco, even if people try to hack the front end for whatever reason. - if (MaxNodeCount >= 0) - { - for (var i = 0; i < splitVals.Length; i++) - { - if (i < MaxNodeCount) - { - val.Add(splitVals[i]); - } - else break; - } - } - else - { - val = splitVals.ToList(); - } - return val.ToArray(); - } - set - { - XmlValue = ConvertToXDocument(value); - } - } - - /// - /// get/set the value for the selected nodes in xml format - /// - public XDocument XmlValue - { - get - { - return ConvertToXDocument(SelectedIds); - } - set - { - if (value == null) - { - SelectedValues.DataSource = null; - PickedValue.Value = ""; - } - else - { - //set the data source for the repeater and hidden field - var nodes = value.Descendants("nodeId"); - SelectedValues.DataSource = nodes; - PickedValue.Value = string.Join(",", nodes.Select(x => x.Value).ToArray()); - } - } - } - - /// - /// The property name being edited with the current data editor. This is used for the min items validation statement. - /// - public string PropertyName { get; set; } - - /// - /// The tree type alias to render - /// - public string TreeToRender { get; set; } - - /// - /// An xpath filter to match nodes that will be disabled from being clicked - /// - public string XPathFilter { get; set; } - - /// - /// The minimum amount of nodes that can be selected - /// - public int MinNodeCount { get; set; } - - /// - /// The maximum amount of nodes that can be selected - /// - public int MaxNodeCount { get; set; } - - /// - /// The start node id - /// - public int StartNodeId { get; set; } - - /// - /// The start node selection type - /// - public NodeSelectionType StartNodeSelectionType { get; set; } - - /// - /// The xpath expression type to select the start node when the StartNodeSelectionType is XPath - /// - public XPathExpressionType StartNodeXPathExpressionType { get; set; } - - /// - /// The XPath expression to use to determine the start node when the StartNodeSelectionType is XPath - /// - public string StartNodeXPathExpression { get; set; } - - /// - /// Gets or sets a value indicating whether [show tool tips]. - /// - /// true if [show tool tips]; otherwise, false. - /// Shows/Hides the tooltip info bubble. - public bool ShowToolTips { get; set; } - - /// - /// The XPathFilterType to match - /// - public XPathFilterType XPathFilterMatchType { get; set; } - - /// - /// Gets or sets a value indicating whether [show thumbnails for media]. - /// - /// - /// true if [show thumbnails for media]; otherwise, false. - /// - /// Whether or not to show thumbnails for media - public bool ShowThumbnailsForMedia { get; set; } - - /// - /// A list of media type names that can have thumbnails (i.e. 'image') - /// - public string[] MediaTypesWithThumbnails { get; set; } - - /// - /// This is set by the data type and allows us to save a cookie value - /// for persistence for the data type. - /// - public int DataTypeDefinitionId { get; set; } - - /// - /// The height of the tree control box in pixels - /// - public int ControlHeight { get; set; } - - #endregion - - /// - /// Initialize the control, make sure children are created - /// - /// An object that contains the event data. - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - EnsureChildControls(); - } - - /// - /// Add the resources (sytles/scripts) - /// - /// - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - - //add the js/css required - this.RegisterEmbeddedClientResource("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerStyles.css", umbraco.cms.businesslogic.datatype.ClientDependencyType.Css); - this.RegisterEmbeddedClientResource("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerScripts.js", umbraco.cms.businesslogic.datatype.ClientDependencyType.Javascript); - - //update the tree type (we need to do this each time because i don't think view state works with these controls) - switch (TreeToRender) - { - case "media": - TreePickerControl.TreeType = "FilteredMediaTree"; - TreePickerControl.App = "media"; - break; - case "content": - default: - TreePickerControl.TreeType = "FilteredContentTree"; - TreePickerControl.App = "content"; - break; - } - - if (Page.IsPostBack) - { - //since it is a post back, bind the data source to the view state values - XmlValue = ConvertToXDocument(SelectedIds); - } - - //bind the repeater if theres a data source, or if there's no datasource but this is a postback (i.e. nodes deleted) - if (SelectedValues.DataSource != null || Page.IsPostBack) - { - SelectedValues.DataBind(); - } - - } - - /// - /// Creates the child controls for this control - /// - protected override void CreateChildControls() - { - base.CreateChildControls(); - - EnsureChildControls(); - - //create the tree control - TreePickerControl = new CustomTreeControl - { - ID = "TreePicker", - IsDialog = true, - ShowContextMenu = false, - DialogMode = TreeDialogModes.id, - Height = Unit.Pixel(ControlHeight), - StartNodeID = StartNodeId - }; - - //create the hidden field - PickedValue = new HiddenField { ID = "PickedValue" }; - - //create the right column - RightColumn = new HtmlGenericControl("div") { ID = "RightColumn" }; - RightColumn.Attributes.Add("class", "right propertypane"); - - //create the repeater - SelectedValues = new Repeater - { - //EnableViewState = false, - ID = "SelectedValues", - ItemTemplate = new SelectedItemsTemplate() - }; - - SelectedValues.ItemDataBound += SelectedValues_ItemDataBound; - - //add the repeater to the right column - RightColumn.Controls.Add(SelectedValues); - - MinItemsValidator = new CustomValidator() - { - ID = "MinItemsValidator", - ErrorMessage = - string.Format(MNTPResources.Val_MinItemsInvalid, MinNodeCount) - }; - MinItemsValidator.ServerValidate += new ServerValidateEventHandler(MinItemsValidator_ServerValidate); - - //add the controls - this.Controls.Add(MinItemsValidator); - this.Controls.Add(TreePickerControl); - this.Controls.Add(PickedValue); - this.Controls.Add(RightColumn); - } - - - - /// - /// Ensure the repeater is data bound - /// - public override void DataBind() - { - base.DataBind(); - SelectedValues.DataBind(); - } - - void MinItemsValidator_ServerValidate(object source, ServerValidateEventArgs args) - { - args.IsValid = true; - if (MinNodeCount > 0 && SelectedIds.Length < MinNodeCount) - { - args.IsValid = false; - } - } - - /// - /// Event handler for the selected node repeater. - /// This will fill in all of the text values, icons, etc.. for nodes based on their ID. - /// - /// - /// - void SelectedValues_ItemDataBound(object sender, RepeaterItemEventArgs e) - { - var liSelectNode = (HtmlGenericControl)e.Item.FindControl("SelectedNodeListItem"); - var lnkSelectNode = (HtmlAnchor)e.Item.FindControl("SelectedNodeLink"); - var litSelectNodeName = (Literal)e.Item.FindControl("SelectedNodeText"); - var infoButton = (HtmlAnchor)e.Item.FindControl("InfoButton"); - - //hide the info button if tooltips are hidden - if (!ShowToolTips) - { - infoButton.Style.Add(HtmlTextWriterStyle.Display, "none"); - } - - var thisNode = (XElement)e.Item.DataItem; - int thisNodeId; - if (int.TryParse(thisNode.Value, out thisNodeId)) - { - umbraco.cms.businesslogic.Content loadedNode; - - try - { - loadedNode = new umbraco.cms.businesslogic.Content(thisNodeId); - - //add the node id - liSelectNode.Attributes["rel"] = thisNodeId.ToString(); - //add the path to be referenced - liSelectNode.Attributes["umb:nodedata"] = loadedNode.Path; - lnkSelectNode.HRef = "javascript:void(0);"; - litSelectNodeName.Text = loadedNode.Text; - - if (loadedNode.IsTrashed) - { - //need to flag this to be removed which will be done after all items are data bound - liSelectNode.Attributes["rel"] = "trashed"; - } - else - { - //we need to set the icon - if (loadedNode.ContentTypeIcon.StartsWith(".spr")) - lnkSelectNode.Attributes["class"] += " " + loadedNode.ContentTypeIcon.TrimStart('.'); - else - { - //it's a real icon, so make it a background image - lnkSelectNode.Style.Add(HtmlTextWriterStyle.BackgroundImage, - string.Format("url('{0}')", IconPath + loadedNode.ContentTypeIcon)); - //set the nospr class since it's not a sprite - lnkSelectNode.Attributes["class"] += " noSpr"; - } - - //show the media preview if media and allowed - if (TreeToRender == "media" && ShowThumbnailsForMedia) - { - var imgPreview = (ImageViewer)e.Item.FindControl("ImgPreview"); - //show the thubmnail controls - imgPreview.Visible = true; - - //add the item class - var item = (HtmlGenericControl)e.Item.FindControl("Item"); - item.Attributes["class"] += " thumb-item"; - - //item.Style.Add(HtmlTextWriterStyle.Height, "50px"); - ////make the content sit beside the item - //var inner = (HtmlGenericControl)e.Item.FindControl("InnerItem"); - //inner.Style.Add(HtmlTextWriterStyle.Width, "224px"); - - //check if it's a thumbnail type element, we need to check both schemas - if (MediaTypesWithThumbnails.Select(x => x.ToUpper()) - .Contains(loadedNode.ContentType.Alias.ToUpper())) - { - imgPreview.MediaId = thisNodeId; - imgPreview.DataBind(); - } - } - } - - } - catch (ArgumentException) - { - //the node no longer exists, so we display a msg - litSelectNodeName.Text = "NODE NO LONGER EXISTS"; - } - } - } - - /// - /// set the nodekey to the id of this datatype - /// - /// - /// this is how get the xpath out of the cookie to know how the tree knows how to filter things. - /// generally the nodekey is used for a string id, but we'll use it for something different. - /// - /// - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - TreePickerControl.NodeKey = this.DataTypeDefinitionId.ToString(); - - SavePersistentValuesForTree(XPathFilter); - } - - /// - /// Override render to control the exact output of what is rendered this includes instantiating the jquery plugin - /// - /// The object that receives the server control content. - /// - /// Generally i don't like to do this but there's a few div's, etc... to render so this makes more sense. - /// - protected override void Render(HtmlTextWriter writer) - { - //
    - //
    - //
    Select items
    - //
    - //
    - // - //
    - //
    - //
    - //
    - - RenderTooltip(writer); - - writer.AddAttribute("class", (!MinItemsValidator.IsValid ? "error " : "") + "multiNodePicker clearfix"); - writer.AddAttribute("id", this.ClientID); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - - writer.AddAttribute("class", "header propertypane"); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.Write("Select Items"); - writer.RenderEndTag(); - writer.RenderEndTag(); - - writer.AddAttribute("class", "left propertypane"); - writer.AddStyleAttribute(HtmlTextWriterStyle.Height, ((ControlHeight + 10).ToString() + "px")); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - //add the tree control here - TreePickerControl.RenderControl(writer); - writer.RenderEndTag(); - - RightColumn.RenderControl(writer); - - //render the hidden field - PickedValue.RenderControl(writer); - - writer.RenderEndTag(); //end multiNodePicker div - - var tooltipAjaxUrl = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + @"/controls/Tree/CustomTreeService.asmx/GetNodeInfo"; - - //add jquery window load event to create the js tree picker - var jsMethod = string.Format("jQuery('#{0}').MultiNodeTreePicker('{1}', {2}, '{3}', {4}, {5}, '{6}', '{7}');", - TreePickerControl.ClientID, - this.ClientID, - MaxNodeCount, - tooltipAjaxUrl, - ShowToolTips.ToString().ToLower(), - (TreeToRender == "media" && ShowThumbnailsForMedia).ToString().ToLower(), - IOHelper.ResolveUrl(SystemDirectories.Umbraco), - TreeToRender); - var js = "jQuery(window).load(function() { " + jsMethod + " });"; - - writer.WriteLine(""); - - } - - /// - /// converts a list of Ids to the XDocument structure - /// - /// The value. - /// - private XDocument ConvertToXDocument(IEnumerable val) - { - if (val.Count() > 0) - { - return new XDocument(new XElement("MultiNodePicker", - new XAttribute("type", TreeToRender), - val.Select(x => new XElement("nodeId", x.ToString())))); - } - else - { - //return null to support recursive values - return null; - - //return an empty node set - //return new XDocument(new XElement("MultiNodePicker")); - } - } - - /// - /// this will render the tooltip object on the page so long as another - /// one hasn't already been registered. There should only be one tooltip. - /// - private void RenderTooltip(HtmlTextWriter writer) - { - if (this.Page.Items.Contains("MNTPTooltip")) - { - return; - } - - //render the tooltip holder - //
    - //
    - //
    - //
    - //this.Page.Controls.AddAt(0, new LiteralControl("
    ")); - writer.AddAttribute("id", "MNTPTooltip"); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.AddAttribute("class", "throbber"); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.RenderEndTag(); //end throbber - writer.AddAttribute("class", "tooltipInfo"); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.RenderEndTag(); //end tooltipInfo - writer.RenderEndTag(); //end tooltipo - - //ensure we add this to our page items so it's not duplicated - this.Page.Items.Add("MNTPTooltip", true); - } - - /// - /// This will update the multi-node tree picker data which is used to store - /// the xpath data and xpath match type for this control id. - /// - /// The xpath. - /// - /// This will save the data into a cookie and also into the request cookie. It must save - /// it to both locations in case the request cookie has been changed and the request cookie - /// is different than the response cookie. - /// - private void SavePersistentValuesForTree(string xpath) - { - - //create the output cookie with all of the values of the request cookie - - var newCookie = HttpContext.Current.Response.Cookies[MNTP_DataType.PersistenceCookieName] ?? new HttpCookie(MNTP_DataType.PersistenceCookieName); - - //store the xpath for this data type definition - newCookie.MntpAddXPathFilter(this.DataTypeDefinitionId, xpath); - //store the match type - newCookie.MntpAddXPathFilterType(this.DataTypeDefinitionId, XPathFilterMatchType); - //store the start node id - newCookie.MntpAddStartNodeId(this.DataTypeDefinitionId, StartNodeId); - //store the start node selection type - newCookie.MntpAddStartNodeSelectionType(this.DataTypeDefinitionId, StartNodeSelectionType); - //store the start node xpath expression type - newCookie.MntpAddStartNodeXPathExpressionType(this.DataTypeDefinitionId, StartNodeXPathExpressionType); - //store the start node xpath expression - newCookie.MntpAddStartNodeXPathExpression(this.DataTypeDefinitionId, StartNodeXPathExpression); - //store the current editing node if found - if (!string.IsNullOrEmpty(HttpContext.Current.Request["id"])) - { - var id = 0; - if (int.TryParse(HttpContext.Current.Request["id"], out id)) - { - newCookie.MntpAddCurrentEditingNode(this.DataTypeDefinitionId, id); - } - } - - HttpContext.Current.Response.Cookies.Add(newCookie); - - //add it to the request cookies too, thus overriding any old data - if (HttpContext.Current.Request.Cookies[MNTP_DataType.PersistenceCookieName] != null && HttpContext.Current.Request.Cookies[MNTP_DataType.PersistenceCookieName].Values.Count > 0) - { - //remove the incoming one and replace with new one - HttpContext.Current.Request.Cookies.Remove(MNTP_DataType.PersistenceCookieName); - } - HttpContext.Current.Request.Cookies.Add(newCookie); - - } - - /// - /// A reference path to where the icons are actually stored as compared to where the tree themes folder is - /// - private static readonly string IconPath = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/images/umbraco/"; - } + /// + /// The user interface to display to the content editor + /// + [ClientDependency(ClientDependencyType.Javascript, "ui/jqueryui.js", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Javascript, "ui/jquery.tooltip.min.js", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Javascript, "controls/Images/ImageViewer.js", "UmbracoRoot")] + public class MNTP_DataEditor : Control, INamingContainer + { + #region Static Constructor + + /// + /// This adds our filtered tree definition to the TreeDefinitionCollection at runtime + /// instead of having to declare it in the database + /// + static MNTP_DataEditor() + { + //NOTE: Before we had locking here but static ctors are always threadsafe + + if (TreeDefinitionCollection.Instance.Any(x => x.TreeType == typeof (FilteredContentTree))) + return; + + + //need to add our tree definitions to the collection. + + //find the content tree to duplicate + var contentTree = TreeDefinitionCollection.Instance.Single(x => x.Tree.Alias.ToUpper() == "CONTENT"); + var filteredContentTree = new TreeDefinition(typeof(FilteredContentTree), + new umbraco.BusinessLogic.ApplicationTree(true, false, 0, + contentTree.Tree.ApplicationAlias, + "FilteredContentTree", + contentTree.Tree.Title, + contentTree.Tree.IconClosed, + contentTree.Tree.IconOpened, + "umbraco.editorControls", + "MultiNodeTreePicker.FilteredContentTree", + contentTree.Tree.Action), + contentTree.App); + + //find the media tree to duplicate + var mediaTree = TreeDefinitionCollection.Instance.Single(x => x.Tree.Alias.ToUpper() == "MEDIA"); + var filteredMediaTree = new TreeDefinition(typeof(FilteredMediaTree), + new umbraco.BusinessLogic.ApplicationTree(true, false, 0, + mediaTree.Tree.ApplicationAlias, + "FilteredMediaTree", + contentTree.Tree.Title, + contentTree.Tree.IconClosed, + contentTree.Tree.IconOpened, + "umbraco.editorControls", + "MultiNodeTreePicker.FilteredMediaTree", + contentTree.Tree.Action), + contentTree.App); + + //add it to the collection at runtime + TreeDefinitionCollection.Instance.Add(filteredContentTree); + TreeDefinitionCollection.Instance.Add(filteredMediaTree); + } + + #endregion + + /// + /// Initializes a new instance of the class. + /// + public MNTP_DataEditor() + { + this.MediaTypesWithThumbnails = new string[] { "image" }; + ShowThumbnailsForMedia = true; + TreeToRender = "content"; + MaxNodeCount = -1; + MinNodeCount = 0; + StartNodeId = uQuery.RootNodeId; + ShowToolTips = true; + ControlHeight = 200; + } + + + + #region Protected members + + /// + /// + /// + protected CustomValidator MinItemsValidator; + + /// + /// + /// + protected CustomTreeControl TreePickerControl; + + /// + /// + /// + protected Repeater SelectedValues; + + /// + /// + /// + protected HiddenField PickedValue; + + /// + /// + /// + protected HtmlGenericControl RightColumn; + #endregion + + #region public Properties + + /// + /// gets/sets the value based on an array of IDs selected + /// + public string[] SelectedIds + { + get + { + List val = new List(); + var splitVals = PickedValue.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + //this will make sure only the node count specified is saved + //into umbraco, even if people try to hack the front end for whatever reason. + if (MaxNodeCount >= 0) + { + for (var i = 0; i < splitVals.Length; i++) + { + if (i < MaxNodeCount) + { + val.Add(splitVals[i]); + } + else break; + } + } + else + { + val = splitVals.ToList(); + } + return val.ToArray(); + } + set + { + XmlValue = ConvertToXDocument(value); + } + } + + /// + /// get/set the value for the selected nodes in xml format + /// + public XDocument XmlValue + { + get + { + return ConvertToXDocument(SelectedIds); + } + set + { + if (value == null) + { + SelectedValues.DataSource = null; + PickedValue.Value = ""; + } + else + { + //set the data source for the repeater and hidden field + var nodes = value.Descendants("nodeId"); + SelectedValues.DataSource = nodes; + PickedValue.Value = string.Join(",", nodes.Select(x => x.Value).ToArray()); + } + } + } + + /// + /// The property name being edited with the current data editor. This is used for the min items validation statement. + /// + public string PropertyName { get; set; } + + /// + /// The tree type alias to render + /// + public string TreeToRender { get; set; } + + /// + /// An xpath filter to match nodes that will be disabled from being clicked + /// + public string XPathFilter { get; set; } + + /// + /// The minimum amount of nodes that can be selected + /// + public int MinNodeCount { get; set; } + + /// + /// The maximum amount of nodes that can be selected + /// + public int MaxNodeCount { get; set; } + + /// + /// The start node id + /// + public int StartNodeId { get; set; } + + /// + /// The start node selection type + /// + public NodeSelectionType StartNodeSelectionType { get; set; } + + /// + /// The xpath expression type to select the start node when the StartNodeSelectionType is XPath + /// + public XPathExpressionType StartNodeXPathExpressionType { get; set; } + + /// + /// The XPath expression to use to determine the start node when the StartNodeSelectionType is XPath + /// + public string StartNodeXPathExpression { get; set; } + + /// + /// Gets or sets a value indicating whether [show tool tips]. + /// + /// true if [show tool tips]; otherwise, false. + /// Shows/Hides the tooltip info bubble. + public bool ShowToolTips { get; set; } + + /// + /// The XPathFilterType to match + /// + public XPathFilterType XPathFilterMatchType { get; set; } + + /// + /// Gets or sets a value indicating whether [show thumbnails for media]. + /// + /// + /// true if [show thumbnails for media]; otherwise, false. + /// + /// Whether or not to show thumbnails for media + public bool ShowThumbnailsForMedia { get; set; } + + /// + /// A list of media type names that can have thumbnails (i.e. 'image') + /// + public string[] MediaTypesWithThumbnails { get; set; } + + /// + /// This is set by the data type and allows us to save a cookie value + /// for persistence for the data type. + /// + public int DataTypeDefinitionId { get; set; } + + /// + /// The height of the tree control box in pixels + /// + public int ControlHeight { get; set; } + + #endregion + + /// + /// Initialize the control, make sure children are created + /// + /// An object that contains the event data. + protected override void OnInit(EventArgs e) + { + base.OnInit(e); + + EnsureChildControls(); + } + + /// + /// Add the resources (sytles/scripts) + /// + /// + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + //add the js/css required + this.RegisterEmbeddedClientResource("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerStyles.css", umbraco.cms.businesslogic.datatype.ClientDependencyType.Css); + this.RegisterEmbeddedClientResource("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerScripts.js", umbraco.cms.businesslogic.datatype.ClientDependencyType.Javascript); + + //update the tree type (we need to do this each time because i don't think view state works with these controls) + switch (TreeToRender) + { + case "media": + TreePickerControl.TreeType = "FilteredMediaTree"; + TreePickerControl.App = "media"; + break; + case "content": + default: + TreePickerControl.TreeType = "FilteredContentTree"; + TreePickerControl.App = "content"; + break; + } + + if (Page.IsPostBack) + { + //since it is a post back, bind the data source to the view state values + XmlValue = ConvertToXDocument(SelectedIds); + } + + //bind the repeater if theres a data source, or if there's no datasource but this is a postback (i.e. nodes deleted) + if (SelectedValues.DataSource != null || Page.IsPostBack) + { + SelectedValues.DataBind(); + } + + } + + /// + /// Creates the child controls for this control + /// + protected override void CreateChildControls() + { + base.CreateChildControls(); + + EnsureChildControls(); + + //create the tree control + TreePickerControl = new CustomTreeControl + { + ID = "TreePicker", + IsDialog = true, + ShowContextMenu = false, + DialogMode = TreeDialogModes.id, + Height = Unit.Pixel(ControlHeight), + StartNodeID = StartNodeId + }; + + //create the hidden field + PickedValue = new HiddenField { ID = "PickedValue" }; + + //create the right column + RightColumn = new HtmlGenericControl("div") { ID = "RightColumn" }; + RightColumn.Attributes.Add("class", "right propertypane"); + + //create the repeater + SelectedValues = new Repeater + { + //EnableViewState = false, + ID = "SelectedValues", + ItemTemplate = new SelectedItemsTemplate() + }; + + SelectedValues.ItemDataBound += SelectedValues_ItemDataBound; + + //add the repeater to the right column + RightColumn.Controls.Add(SelectedValues); + + MinItemsValidator = new CustomValidator() + { + ID = "MinItemsValidator", + ErrorMessage = + string.Format(MNTPResources.Val_MinItemsInvalid, MinNodeCount) + }; + MinItemsValidator.ServerValidate += new ServerValidateEventHandler(MinItemsValidator_ServerValidate); + + //add the controls + this.Controls.Add(MinItemsValidator); + this.Controls.Add(TreePickerControl); + this.Controls.Add(PickedValue); + this.Controls.Add(RightColumn); + } + + + + /// + /// Ensure the repeater is data bound + /// + public override void DataBind() + { + base.DataBind(); + SelectedValues.DataBind(); + } + + void MinItemsValidator_ServerValidate(object source, ServerValidateEventArgs args) + { + args.IsValid = true; + if (MinNodeCount > 0 && SelectedIds.Length < MinNodeCount) + { + args.IsValid = false; + } + } + + /// + /// Event handler for the selected node repeater. + /// This will fill in all of the text values, icons, etc.. for nodes based on their ID. + /// + /// + /// + void SelectedValues_ItemDataBound(object sender, RepeaterItemEventArgs e) + { + var liSelectNode = (HtmlGenericControl)e.Item.FindControl("SelectedNodeListItem"); + var lnkSelectNode = (HtmlAnchor)e.Item.FindControl("SelectedNodeLink"); + var litSelectNodeName = (Literal)e.Item.FindControl("SelectedNodeText"); + var infoButton = (HtmlAnchor)e.Item.FindControl("InfoButton"); + + //hide the info button if tooltips are hidden + if (!ShowToolTips) + { + infoButton.Style.Add(HtmlTextWriterStyle.Display, "none"); + } + + var thisNode = (XElement)e.Item.DataItem; + int thisNodeId; + if (int.TryParse(thisNode.Value, out thisNodeId)) + { + umbraco.cms.businesslogic.Content loadedNode; + + try + { + loadedNode = new umbraco.cms.businesslogic.Content(thisNodeId); + + //add the node id + liSelectNode.Attributes["rel"] = thisNodeId.ToString(); + //add the path to be referenced + liSelectNode.Attributes["umb:nodedata"] = loadedNode.Path; + lnkSelectNode.HRef = "javascript:void(0);"; + litSelectNodeName.Text = loadedNode.Text; + + if (loadedNode.IsTrashed) + { + //need to flag this to be removed which will be done after all items are data bound + liSelectNode.Attributes["rel"] = "trashed"; + } + else + { + //we need to set the icon + if (loadedNode.ContentTypeIcon.StartsWith(".spr")) + lnkSelectNode.Attributes["class"] += " " + loadedNode.ContentTypeIcon.TrimStart('.'); + else + { + //it's a real icon, so make it a background image + lnkSelectNode.Style.Add(HtmlTextWriterStyle.BackgroundImage, + string.Format("url('{0}')", IconPath + loadedNode.ContentTypeIcon)); + //set the nospr class since it's not a sprite + lnkSelectNode.Attributes["class"] += " noSpr"; + } + + //show the media preview if media and allowed + if (TreeToRender == "media" && ShowThumbnailsForMedia) + { + var imgPreview = (ImageViewer)e.Item.FindControl("ImgPreview"); + //show the thubmnail controls + imgPreview.Visible = true; + + //add the item class + var item = (HtmlGenericControl)e.Item.FindControl("Item"); + item.Attributes["class"] += " thumb-item"; + + //item.Style.Add(HtmlTextWriterStyle.Height, "50px"); + ////make the content sit beside the item + //var inner = (HtmlGenericControl)e.Item.FindControl("InnerItem"); + //inner.Style.Add(HtmlTextWriterStyle.Width, "224px"); + + //check if it's a thumbnail type element, we need to check both schemas + if (MediaTypesWithThumbnails.Select(x => x.ToUpper()) + .Contains(loadedNode.ContentType.Alias.ToUpper())) + { + imgPreview.MediaId = thisNodeId; + imgPreview.DataBind(); + } + } + } + + } + catch (ArgumentException) + { + //the node no longer exists, so we display a msg + litSelectNodeName.Text = "NODE NO LONGER EXISTS"; + } + } + } + + /// + /// set the nodekey to the id of this datatype + /// + /// + /// this is how get the xpath out of the cookie to know how the tree knows how to filter things. + /// generally the nodekey is used for a string id, but we'll use it for something different. + /// + /// + protected override void OnPreRender(EventArgs e) + { + base.OnPreRender(e); + + TreePickerControl.NodeKey = this.DataTypeDefinitionId.ToString(); + + SavePersistentValuesForTree(XPathFilter); + } + + /// + /// Override render to control the exact output of what is rendered this includes instantiating the jquery plugin + /// + /// The object that receives the server control content. + /// + /// Generally i don't like to do this but there's a few div's, etc... to render so this makes more sense. + /// + protected override void Render(HtmlTextWriter writer) + { + //
    + //
    + //
    Select items
    + //
    + //
    + // + //
    + //
    + //
    + //
    + + RenderTooltip(writer); + + writer.AddAttribute("class", (!MinItemsValidator.IsValid ? "error " : "") + "multiNodePicker clearfix"); + writer.AddAttribute("id", this.ClientID); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + + writer.AddAttribute("class", "header propertypane"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.Write("Select Items"); + writer.RenderEndTag(); + writer.RenderEndTag(); + + writer.AddAttribute("class", "left propertypane"); + writer.AddStyleAttribute(HtmlTextWriterStyle.Height, ((ControlHeight + 10).ToString() + "px")); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + //add the tree control here + TreePickerControl.RenderControl(writer); + writer.RenderEndTag(); + + RightColumn.RenderControl(writer); + + //render the hidden field + PickedValue.RenderControl(writer); + + writer.RenderEndTag(); //end multiNodePicker div + + var tooltipAjaxUrl = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + @"/controls/Tree/CustomTreeService.asmx/GetNodeInfo"; + + //add jquery window load event to create the js tree picker + var jsMethod = string.Format("jQuery('#{0}').MultiNodeTreePicker('{1}', {2}, '{3}', {4}, {5}, '{6}', '{7}');", + TreePickerControl.ClientID, + this.ClientID, + MaxNodeCount, + tooltipAjaxUrl, + ShowToolTips.ToString().ToLower(), + (TreeToRender == "media" && ShowThumbnailsForMedia).ToString().ToLower(), + IOHelper.ResolveUrl(SystemDirectories.Umbraco), + TreeToRender); + var js = "jQuery(window).load(function() { " + jsMethod + " });"; + + writer.WriteLine(""); + + } + + /// + /// converts a list of Ids to the XDocument structure + /// + /// The value. + /// + private XDocument ConvertToXDocument(IEnumerable val) + { + if (val.Count() > 0) + { + return new XDocument(new XElement("MultiNodePicker", + new XAttribute("type", TreeToRender), + val.Select(x => new XElement("nodeId", x.ToString())))); + } + else + { + //return null to support recursive values + return null; + + //return an empty node set + //return new XDocument(new XElement("MultiNodePicker")); + } + } + + /// + /// this will render the tooltip object on the page so long as another + /// one hasn't already been registered. There should only be one tooltip. + /// + private void RenderTooltip(HtmlTextWriter writer) + { + if (this.Page.Items.Contains("MNTPTooltip")) + { + return; + } + + //render the tooltip holder + //
    + //
    + //
    + //
    + //this.Page.Controls.AddAt(0, new LiteralControl("
    ")); + writer.AddAttribute("id", "MNTPTooltip"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.AddAttribute("class", "throbber"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.RenderEndTag(); //end throbber + writer.AddAttribute("class", "tooltipInfo"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.RenderEndTag(); //end tooltipInfo + writer.RenderEndTag(); //end tooltipo + + //ensure we add this to our page items so it's not duplicated + this.Page.Items.Add("MNTPTooltip", true); + } + + /// + /// This will update the multi-node tree picker data which is used to store + /// the xpath data and xpath match type for this control id. + /// + /// The xpath. + /// + /// This will save the data into a cookie and also into the request cookie. It must save + /// it to both locations in case the request cookie has been changed and the request cookie + /// is different than the response cookie. + /// + private void SavePersistentValuesForTree(string xpath) + { + + //create the output cookie with all of the values of the request cookie + + var newCookie = HttpContext.Current.Response.Cookies[MNTP_DataType.PersistenceCookieName] ?? new HttpCookie(MNTP_DataType.PersistenceCookieName); + + //store the xpath for this data type definition + newCookie.MntpAddXPathFilter(this.DataTypeDefinitionId, xpath); + //store the match type + newCookie.MntpAddXPathFilterType(this.DataTypeDefinitionId, XPathFilterMatchType); + //store the start node id + newCookie.MntpAddStartNodeId(this.DataTypeDefinitionId, StartNodeId); + //store the start node selection type + newCookie.MntpAddStartNodeSelectionType(this.DataTypeDefinitionId, StartNodeSelectionType); + //store the start node xpath expression type + newCookie.MntpAddStartNodeXPathExpressionType(this.DataTypeDefinitionId, StartNodeXPathExpressionType); + //store the start node xpath expression + newCookie.MntpAddStartNodeXPathExpression(this.DataTypeDefinitionId, StartNodeXPathExpression); + //store the current editing node if found + if (!string.IsNullOrEmpty(HttpContext.Current.Request["id"])) + { + var id = 0; + if (int.TryParse(HttpContext.Current.Request["id"], out id)) + { + newCookie.MntpAddCurrentEditingNode(this.DataTypeDefinitionId, id); + } + } + + HttpContext.Current.Response.Cookies.Add(newCookie); + + //add it to the request cookies too, thus overriding any old data + if (HttpContext.Current.Request.Cookies[MNTP_DataType.PersistenceCookieName] != null && HttpContext.Current.Request.Cookies[MNTP_DataType.PersistenceCookieName].Values.Count > 0) + { + //remove the incoming one and replace with new one + HttpContext.Current.Request.Cookies.Remove(MNTP_DataType.PersistenceCookieName); + } + HttpContext.Current.Request.Cookies.Add(newCookie); + + } + + /// + /// A reference path to where the icons are actually stored as compared to where the tree themes folder is + /// + private static readonly string IconPath = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/images/umbraco/"; + } } diff --git a/test/umbraco.Test/Umbraco.LegacyTests.csproj b/test/umbraco.Test/Umbraco.LegacyTests.csproj index 2876f15ebd..942d01a6a4 100644 --- a/test/umbraco.Test/Umbraco.LegacyTests.csproj +++ b/test/umbraco.Test/Umbraco.LegacyTests.csproj @@ -122,10 +122,8 @@ Properties\SolutionInfo.cs - -