diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 1311593e90..ed9b7c5a9d 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -303,6 +303,8 @@ namespace Umbraco.Core connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString; } Initialize(providerName, connString); + + DetermineSqlServerVersion(); } else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false) { @@ -339,6 +341,8 @@ namespace Umbraco.Core //Remove the legacy connection string, so we don't end up in a loop if something goes wrong. GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName); + + DetermineSqlServerVersion(); } else { @@ -372,6 +376,49 @@ namespace Umbraco.Core Initialize(providerName); } + /// + /// Set the lazy resolution of determining the SQL server version if that is the db type we're using + /// + private void DetermineSqlServerVersion() + { + + var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider; + if (sqlServerSyntax != null) + { + //this will not execute now, it is lazy so will only execute when we need to actually know + // the sql server version. + sqlServerSyntax.VersionName = new Lazy(() => + { + try + { + var database = this._factory.CreateDatabase(); + + var version = database.ExecuteScalar("SELECT SERVERPROPERTY('productversion')"); + var firstPart = version.Split('.')[0]; + switch (firstPart) + { + case "11": + return SqlServerVersionName.V2012; + case "10": + return SqlServerVersionName.V2008; + case "9": + return SqlServerVersionName.V2005; + case "8": + return SqlServerVersionName.V2000; + case "7": + return SqlServerVersionName.V7; + default: + return SqlServerVersionName.Other; + } + } + catch (Exception) + { + return SqlServerVersionName.Invalid; + } + }); + } + } + internal DatabaseSchemaResult ValidateDatabaseSchema() { if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName))) @@ -463,6 +510,8 @@ namespace Umbraco.Core message = message + "

Upgrade completed!

"; } + //now that everything is done, we need to determine the version of SQL server that is executing + LogHelper.Info("Database configuration status: " + message); return new Result { Message = message, Success = true, Percentage = "100" }; diff --git a/src/Umbraco.Core/Models/PropertyExtensions.cs b/src/Umbraco.Core/Models/PropertyExtensions.cs index 4a2fd8c289..8fb94e8c0c 100644 --- a/src/Umbraco.Core/Models/PropertyExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyExtensions.cs @@ -15,7 +15,12 @@ namespace Umbraco.Core.Models /// Xml of the property and its value public static XElement ToXml(this Property property) { - string nodeName = UmbracoSettings.UseLegacyXmlSchema ? "data" : property.Alias.ToSafeAlias(); + return property.ToXml(ApplicationContext.Current.Services.DataTypeService); + } + + internal static XElement ToXml(this Property property, IDataTypeService dataTypeService) + { + var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "data" : property.Alias.ToSafeAlias(); var xd = new XmlDocument(); var xmlNode = xd.CreateNode(XmlNodeType.Element, nodeName, ""); @@ -37,6 +42,16 @@ namespace Umbraco.Core.Models //var dataType = ApplicationContext.Current.Services.DataTypeService.GetDataTypeDefinitionById(property.PropertyType.DataTypeDefinitionId); //if (dataType == null) throw new InvalidOperationException("No data type definition found with id " + property.PropertyType.DataTypeDefinitionId); + //We've already got the value for the property so we're going to give it to the + // data type's data property so it doesn't go re-look up the value from the db again. + var defaultData = dt.Data as IDataValueSetter; + if (defaultData != null) + { + defaultData.SetValue(property.Value, property.PropertyType.DataTypeDatabaseType.ToString()); + } + + xmlNode.AppendChild(dt.Data.ToXMl(xd)); + var propertyEditor = PropertyEditorResolver.Current.GetById(property.PropertyType.DataTypeId); if (propertyEditor != null) { diff --git a/src/Umbraco.Core/Models/PropertyTypeExtensions.cs b/src/Umbraco.Core/Models/PropertyTypeExtensions.cs index 62a6a6fd1d..fef05e5e47 100644 --- a/src/Umbraco.Core/Models/PropertyTypeExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTypeExtensions.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Services; using umbraco.interfaces; namespace Umbraco.Core.Models @@ -10,6 +11,7 @@ namespace Umbraco.Core.Models /// /// PropertyType that references a DataType /// Id of the Property which references this DataType through its PropertyType + /// /// /// /// This extension method is left internal because we don't want to take @@ -17,10 +19,10 @@ namespace Umbraco.Core.Models /// be replaced by PropertyEditors. It is however needed to generate xml /// for a property/propertytype when publishing. /// - internal static IDataType DataType(this PropertyType propertyType, int propertyId) + internal static IDataType DataType(this PropertyType propertyType, int propertyId, IDataTypeService dataTypeService) { Mandate.ParameterNotNull(propertyType, "propertyType"); - var dataType = ApplicationContext.Current.Services.DataTypeService.GetDataTypeById(propertyType.DataTypeId); + var dataType = dataTypeService.GetDataTypeById(propertyType.DataTypeId); if (dataType == null) throw new InvalidOperationException("No IDataType found for control ID " + propertyType.DataTypeId); diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index c962224689..eb10bf7f87 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -51,7 +51,10 @@ namespace Umbraco.Core.Persistence try { - if (SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider) + //if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts. + var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider; + if ((sqlServerSyntax != null && (int)sqlServerSyntax.VersionName.Value < (int)SqlServerVersionName.V2008) + || SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider) { //SqlCe doesn't support bulk insert statements! diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index acbc97d445..ed031d9ab0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -339,19 +339,22 @@ namespace Umbraco.Core.Persistence.Repositories } } + //Look up (newest) entries by id in cmsDocument table to set newest = false + //NOTE: This should only be done for all other versions then the current one, so we don't cause the same entry to be updated multiple times. + var documentDtos = + Database.Query( + "WHERE nodeId = @Id AND newest = @IsNewest AND NOT(versionId = @VersionId)", + new {Id = entity.Id, IsNewest = true, VersionId = dto.ContentVersionDto.VersionId}); + foreach (var documentDto in documentDtos) + { + var docDto = documentDto; + docDto.Newest = false; + Database.Update(docDto); + } + var contentVersionDto = dto.ContentVersionDto; if (shouldCreateNewVersion) { - //Look up (newest) entries by id in cmsDocument table to set newest = false - //NOTE: This is only relevant when a new version is created, which is why its done inside this if-statement. - var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true }); - foreach (var documentDto in documentDtos) - { - var docDto = documentDto; - docDto.Newest = false; - Database.Update(docDto); - } - //Create a new version - cmsContentVersion //Assumes a new Version guid and Version date (modified date) has been set Database.Insert(contentVersionDto); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 2de2209ab4..3e37674ddf 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -13,6 +13,20 @@ namespace Umbraco.Core.Persistence.SqlSyntax public static ISqlSyntaxProvider Provider { get { return new SqlServerSyntaxProvider(); } } } + /// + /// Represents the version name of SQL server (i.e. the year 2008, 2005, etc...) + /// + internal enum SqlServerVersionName + { + Invalid = -1, + V7 = 0, + V2000 = 1, + V2005 = 2, + V2008 = 3, + V2012 = 4, + Other = 5 + } + /// /// Represents an SqlSyntaxProvider for Sql Server /// @@ -36,6 +50,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax InitColumnTypeMap(); } + /// + /// Gets/sets the version of the current SQL server instance + /// + internal Lazy VersionName { get; set; } + public override string GetQuotedTableName(string tableName) { return string.Format("[{0}]", tableName); diff --git a/src/Umbraco.Core/Publishing/PublishStatus.cs b/src/Umbraco.Core/Publishing/PublishStatus.cs index 415b758965..aee9a1fafe 100644 --- a/src/Umbraco.Core/Publishing/PublishStatus.cs +++ b/src/Umbraco.Core/Publishing/PublishStatus.cs @@ -8,15 +8,14 @@ namespace Umbraco.Core.Publishing /// internal class PublishStatus { - public IContent ContentItem { get; private set; } - public PublishStatusType StatusType { get; internal set; } - - /// - /// Gets sets the invalid properties if the status failed due to validation. - /// - public IEnumerable InvalidProperties { get; set; } + public PublishStatus() + { + //initialize + InvalidProperties = new List(); + } public PublishStatus(IContent content, PublishStatusType statusType) + : this() { ContentItem = content; StatusType = statusType; @@ -29,6 +28,13 @@ namespace Umbraco.Core.Publishing : this(content, PublishStatusType.Success) { } + + public IContent ContentItem { get; private set; } + public PublishStatusType StatusType { get; internal set; } + /// + /// Gets sets the invalid properties if the status failed due to validation. + /// + public IEnumerable InvalidProperties { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs index 67cff98ac4..1cee710f19 100644 --- a/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs +++ b/src/Umbraco.Core/Security/UmbracoBackOfficeIdentity.cs @@ -1,4 +1,6 @@ -using System.Web.Security; +using System; +using System.Web; +using System.Web.Security; using Newtonsoft.Json; namespace Umbraco.Core.Security @@ -15,6 +17,7 @@ namespace Umbraco.Core.Security : base(ticket) { UserData = ticket.UserData; + EnsureDeserialized(); } protected readonly string UserData; @@ -24,54 +27,33 @@ namespace Umbraco.Core.Security { get { - EnsureDeserialized(); return DeserializedData.StartContentNode; } } public int StartMediaNode { - get - { - EnsureDeserialized(); - return DeserializedData.StartMediaNode; - } + get { return DeserializedData.StartMediaNode; } } public string[] AllowedApplications { - get - { - EnsureDeserialized(); - return DeserializedData.AllowedApplications; - } + get { return DeserializedData.AllowedApplications; } } public object Id { - get - { - EnsureDeserialized(); - return DeserializedData.Id; - } + get { return DeserializedData.Id; } } public string RealName { - get - { - EnsureDeserialized(); - return DeserializedData.RealName; - } + get { return DeserializedData.RealName; } } public string Culture { - get - { - EnsureDeserialized(); - return DeserializedData.Culture; - } + get { return DeserializedData.Culture; } } //public int SessionTimeout @@ -85,24 +67,42 @@ namespace Umbraco.Core.Security public string[] Roles { - get - { - EnsureDeserialized(); - return DeserializedData.Roles; - } + get { return DeserializedData.Roles; } } + /// + /// This will ensure we only deserialize once + /// + /// + /// For performance reasons, we'll also check if there's an http context available, + /// if so, we'll chuck our instance in there so that we only deserialize once per request. + /// protected void EnsureDeserialized() { if (DeserializedData != null) return; - + + if (HttpContext.Current != null) + { + //check if we've already done this in this request + var data = HttpContext.Current.Items[typeof(UmbracoBackOfficeIdentity)] as UserData; + if (data != null) + { + DeserializedData = data; + return; + } + } + if (string.IsNullOrEmpty(UserData)) { - DeserializedData = new UserData(); - return; + throw new NullReferenceException("The " + typeof(UserData) + " found in the ticket cannot be empty"); + } + DeserializedData = JsonConvert.DeserializeObject(UserData); + + if (HttpContext.Current != null) + { + HttpContext.Current.Items[typeof (UmbracoBackOfficeIdentity)] = DeserializedData; } - DeserializedData = JsonConvert.DeserializeObject(UserData); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 8aabfed80f..37da38126e 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1479,7 +1479,12 @@ namespace Umbraco.Core.Services LogHelper.Info( string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.", content.Name, content.Id)); - result.Add(new Attempt(false, new PublishStatus(content, PublishStatusType.FailedContentInvalid))); + result.Add( + new Attempt(false, + new PublishStatus(content, PublishStatusType.FailedContentInvalid) + { + InvalidProperties = ((ContentBase) content).LastInvalidProperties + })); return result; } diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index a873343158..5de4285469 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -13,6 +13,7 @@ namespace Umbraco.Core /// public static class UriExtensions { + /// /// Checks if the current uri is a back office request /// @@ -20,11 +21,14 @@ namespace Umbraco.Core /// internal static bool IsBackOfficeRequest(this Uri url) { + var authority = url.GetLeftPart(UriPartial.Authority); var afterAuthority = url.GetLeftPart(UriPartial.Query) .TrimStart(authority) .TrimStart("/"); + + //check if this is in the umbraco back office return afterAuthority.InvariantStartsWith(GlobalSettings.Path.TrimStart("/")); } diff --git a/src/Umbraco.Tests/Models/DataValueSetterTests.cs b/src/Umbraco.Tests/Models/DataValueSetterTests.cs new file mode 100644 index 0000000000..db59217166 --- /dev/null +++ b/src/Umbraco.Tests/Models/DataValueSetterTests.cs @@ -0,0 +1,102 @@ +using System; +using System.Diagnostics; +using System.Xml; +using NUnit.Framework; +using Rhino.Mocks; +using Rhino.Mocks.Interfaces; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers; +using umbraco.cms.businesslogic.datatype; +using umbraco.interfaces; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DataValueSetterTests : BaseUmbracoApplicationTest + { + protected override void FreezeResolution() + { + ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper()); + base.FreezeResolution(); + } + + [Test] + public void LoadValueFromDatabase_Is_Not_Called_When_SetValue_Is_Used() + { + // Arrange + var baseDataType = MockRepository.GenerateStub(); + var dataTypeData = MockRepository.GenerateMock(baseDataType); + dataTypeData.Stub(x => x.Value).CallOriginalMethod(OriginalCallOptions.NoExpectation); + + // Act + + ((IDataValueSetter)dataTypeData).SetValue("Hello world", DataTypeDatabaseType.Nvarchar.ToString()); + var val = dataTypeData.Value; + + // Assert + + dataTypeData.AssertWasNotCalled(data => data.LoadValueFromDatabase()); + } + + [Test] + public void LoadValueFromDatabase_Is_Called_When_SetValue_Is_Not_Used() + { + // Arrange + var baseDataType = MockRepository.GenerateStub(); + var dataTypeData = MockRepository.GenerateMock(baseDataType); + dataTypeData + .Stub(data => data.LoadValueFromDatabase()).WhenCalled(invocation => Debug.WriteLine("asdf")); + dataTypeData.Stub(x => x.Value).CallOriginalMethod(OriginalCallOptions.NoExpectation); + + // Act + + var val = dataTypeData.Value; + + // Assert + + dataTypeData.AssertWasCalled(data => data.LoadValueFromDatabase()); + } + + [Test] + public void SetValue_Is_Called_When_Executing_ToXml_On_A_Property_With_DataType_That_Implements_IDataValueSetter() + { + // Arrange + var dataTypeId = Guid.NewGuid(); + + var dataTypeData = MockRepository.GenerateMock(); + dataTypeData + .Stub(data => data.ToXMl(Arg.Is.Anything)) + .Return(null) // you have to call Return() even though we're about to override it + .WhenCalled(invocation => + { + var xmlDoc = (XmlDocument) invocation.Arguments[0]; + invocation.ReturnValue = xmlDoc.CreateElement("test"); + }); + + var dataType = MockRepository.GenerateStub(); + dataType.Stub(type => type.Data).Return(dataTypeData); + + var dataTypeSvc = MockRepository.GenerateStub(); + dataTypeSvc.Stub(service => service.GetDataTypeById(dataTypeId)).Return(dataType); + + var property = new Property( + 1234, + Guid.NewGuid(), + new PropertyType(dataTypeId, DataTypeDatabaseType.Nvarchar) + { + Alias = "test" + }, "Hello world"); + + // Act + + var xml = property.ToXml(dataTypeSvc); + + // Assert + + ((IDataValueSetter)dataTypeData).AssertWasCalled(setter => setter.SetValue("Hello world", DataTypeDatabaseType.Nvarchar.ToString())); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/PackagingServiceTests.cs b/src/Umbraco.Tests/Services/PackagingServiceTests.cs new file mode 100644 index 0000000000..abdfb2f9f3 --- /dev/null +++ b/src/Umbraco.Tests/Services/PackagingServiceTests.cs @@ -0,0 +1,41 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Services +{ + //[TestFixture] + //public class PackagingServiceTests : BaseServiceTest + //{ + // [Test] + // public void Export_Content() + // { + // var yesNo = DataTypesResolver.Current.GetById(new Guid(Constants.PropertyEditors.TrueFalse)); + // var txtField = DataTypesResolver.Current.GetById(new Guid(Constants.PropertyEditors.Textbox)); + + // var contentWithDataType = MockedContentTypes.CreateSimpleContentType( + // "test", + // "Test", + // new PropertyTypeCollection( + // new PropertyType[] + // { + // new PropertyType(new DataTypeDefinition(-1, txtField.Id) + // { + // Name = "Testing Textfield", DatabaseType = DataTypeDatabaseType.Ntext + // }), + // new PropertyType(new DataTypeDefinition(-1, yesNo.Id) + // { + // Name = "Testing intfield", DatabaseType = DataTypeDatabaseType.Integer + // }) + // })); + + // var content = MockedContent.CreateSimpleContent(contentWithDataType); + // content.Name = "Test"; + + // var exported = ServiceContext.PackagingService.Export(content); + + // } + //} +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 7e453ca3c2..dd75809a5f 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -42,28 +42,39 @@ namespace Umbraco.Tests.TestHelpers private static volatile bool _firstRunInTestSession = true; private static readonly object Locker = new object(); private bool _firstTestInFixture = true; - private DefaultDatabaseFactory _dbFactory; //Used to flag if its the first test in the current session private bool _isFirstRunInTestSession = false; //Used to flag if its the first test in the current fixture private bool _isFirstTestInFixture = false; + private ApplicationContext _appContext; + [SetUp] public override void Initialize() { InitializeFirstRunFlags(); - - _dbFactory = new DefaultDatabaseFactory( - GetDbConnectionString(), - GetDbProviderName()); - - base.Initialize(); - + var path = TestHelper.CurrentAssemblyDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", path); - DatabaseContext.Initialize(_dbFactory.ProviderName, _dbFactory.ConnectionString); + var dbFactory = new DefaultDatabaseFactory( + GetDbConnectionString(), + GetDbProviderName()); + _appContext = new ApplicationContext( + //assign the db context + new DatabaseContext(dbFactory), + //assign the service context + new ServiceContext(new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy()), + //disable cache + false) + { + IsReady = true + }; + + base.Initialize(); + + DatabaseContext.Initialize(dbFactory.ProviderName, dbFactory.ConnectionString); CreateSqlCeDatabase(); @@ -75,18 +86,7 @@ namespace Umbraco.Tests.TestHelpers protected override void SetupApplicationContext() { - //disable cache - var cacheHelper = new CacheHelper(new NullCacheProvider(), false); - - ApplicationContext.Current = new ApplicationContext( - //assign the db context - new DatabaseContext(_dbFactory), - //assign the service context - new ServiceContext(new PetaPocoUnitOfWorkProvider(), new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper), - cacheHelper) - { - IsReady = true - }; + ApplicationContext.Current = _appContext; } /// diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 677a425fa9..76fc4917f6 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -33,6 +33,7 @@ namespace Umbraco.Tests.TestHelpers SetupPluginManager(); SetupApplicationContext(); InitializeMappers(); + FreezeResolution(); } @@ -48,7 +49,7 @@ namespace Umbraco.Tests.TestHelpers ApplicationContext.Current = null; ResetPluginManager(); } - + private void InitializeMappers() { Mapper.Initialize(configuration => diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index de74ccf7ad..9245eca8f5 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -198,10 +198,12 @@ + + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 92b5e584bf..75408644a6 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -937,6 +937,7 @@ + @@ -957,6 +958,7 @@ + @@ -979,6 +981,7 @@ + @@ -998,6 +1001,7 @@ + @@ -1050,6 +1054,7 @@ + @@ -1079,6 +1084,7 @@ + @@ -1112,6 +1118,7 @@ + @@ -1161,6 +1168,7 @@ + @@ -1191,6 +1199,7 @@ + @@ -1215,6 +1224,7 @@ + @@ -1244,6 +1254,7 @@ + @@ -1267,6 +1278,7 @@ + @@ -1281,8 +1293,12 @@ + + + + @@ -1291,6 +1307,8 @@ + + @@ -1299,8 +1317,12 @@ + + + + @@ -1308,8 +1330,10 @@ + + @@ -1318,6 +1342,7 @@ + @@ -1333,6 +1358,8 @@ + + @@ -1374,6 +1401,7 @@ + diff --git a/src/Umbraco.Web.UI/config/metablogConfig.config b/src/Umbraco.Web.UI/config/metablogConfig.config index 83cc2c1aa1..5621dbee75 100644 --- a/src/Umbraco.Web.UI/config/metablogConfig.config +++ b/src/Umbraco.Web.UI/config/metablogConfig.config @@ -5,7 +5,7 @@ 0 1080 False - Home + Base @@ -14,6 +14,6 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/controls/passwordChanger.ascx b/src/Umbraco.Web.UI/umbraco/controls/passwordChanger.ascx index fccd948dec..b9da0cbac7 100644 --- a/src/Umbraco.Web.UI/umbraco/controls/passwordChanger.ascx +++ b/src/Umbraco.Web.UI/umbraco/controls/passwordChanger.ascx @@ -1,16 +1,34 @@ <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="passwordChanger.ascx.cs" Inherits="umbraco.controls.passwordChanger" %> -Change password
+ - \ No newline at end of file +Change password
+ + diff --git a/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs b/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs index 4210fc1e3e..e545e65311 100644 --- a/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs +++ b/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs @@ -51,14 +51,6 @@ namespace Umbraco.Web.UI.Pages Response.Redirect(SystemDirectories.Umbraco + "/logout.aspx?redir=" + Server.UrlEncode(Request.RawUrl), true); } } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(ui.Culture(Security.CurrentUser)); - System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture; - } /// /// Gets/sets the app that this page is assigned to diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs index ab8579db27..91c6a8a4e0 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs @@ -249,7 +249,7 @@ namespace umbraco.cms.presentation //update UI and set document properties PerformSaveLogic(); - + //persist the document _document.Save(); @@ -257,7 +257,7 @@ namespace umbraco.cms.presentation BusinessLogic.Actions.Action.RunActionHandlers(_document, ActionUpdate.Instance); ClientTools.ShowSpeechBubble( - speechBubbleIcon.save, ui.Text("speechBubbles", "editContentSavedHeader", null), + speechBubbleIcon.save, ui.Text("speechBubbles", "editContentSavedHeader", null), ui.Text("speechBubbles", "editContentSavedText", null)); ClientTools.SyncTree(_document.Path, true); @@ -291,7 +291,7 @@ namespace umbraco.cms.presentation { //update UI and set document properties PerformSaveLogic(); - + //the business logic here will check to see if the doc can actually be published and will return the // appropriate result so we can display the correct error messages (or success). var savePublishResult = _document.SaveAndPublishWithResult(UmbracoUser); @@ -309,7 +309,7 @@ namespace umbraco.cms.presentation _documentHasPublishedVersion = _document.Content.HasPublishedVersion(); } - + ClientTools.SyncTree(_document.Path, true); } @@ -320,8 +320,8 @@ namespace umbraco.cms.presentation case PublishStatusType.Success: case PublishStatusType.SuccessAlreadyPublished: ClientTools.ShowSpeechBubble( - speechBubbleIcon.save, - ui.Text("speechBubbles", "editContentPublishedHeader", UmbracoUser), + speechBubbleIcon.save, + ui.Text("speechBubbles", "editContentPublishedHeader", UmbracoUser), ui.Text("speechBubbles", "editContentPublishedText", UmbracoUser)); break; case PublishStatusType.FailedPathNotPublished: @@ -550,23 +550,40 @@ namespace umbraco.cms.presentation } menuItem.ImageURL = SystemDirectories.Umbraco + "/images/editor/vis.gif"; - // Fix for U4-682, if there's no template, disable the preview button - if (_document.Template != -1) + + if (EnablePreviewButton()) { menuItem.AltText = ui.Text("buttons", "showPage", UmbracoUser); menuItem.OnClickCommand = "window.open('dialogs/preview.aspx?id=" + id + "','umbPreview')"; } else { - string showPageDisabledText = ui.Text("buttons", "showPageDisabled", UmbracoUser); + var showPageDisabledText = ui.Text("buttons", "showPageDisabled", UmbracoUser); if (showPageDisabledText.StartsWith("[")) - showPageDisabledText = ui.GetText("buttons", "showPageDisabled", null, "en"); ; + showPageDisabledText = ui.GetText("buttons", "showPageDisabled", null, "en"); menuItem.AltText = showPageDisabledText; - ((Image)menuItem).Attributes.Add("style", "opacity: 0.5"); + ((Image) menuItem).Attributes.Add("style", "opacity: 0.5"); } } + private bool EnablePreviewButton() + { + // Fix for U4-862, if there's no template, disable the preview button + // Fixed again for U4-2587, apparently at some point "no template" changed from -1 to 0? -SJ + // Now also catches when template doesn't exist any more or is not allowed any more + // Don't think there's a better way to check if the template exists besides trying to instantiate it.. + try + { + var template = new businesslogic.template.Template(_document.Template); + // If template is found check if it's in the list of allowed templates for this document + return _document.Content.ContentType.AllowedTemplates.ToList().Any(t => t.Id == template.Id); + } + catch (Exception) { } + + return false; + } + /// /// JsInclude1 control. /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index 55303bc219..1569a2d110 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -348,16 +348,12 @@ namespace umbraco.cms.presentation.user } } } - - #region Web Form Designer generated code - + protected override void OnInit(EventArgs e) { - // - // CODEGEN: This call is required by the ASP.NET Web Form Designer. - // - InitializeComponent(); - base.OnInit(e); + //lapps.SelectionMode = ListSelectionMode.Multiple; + lapps.RepeatLayout = RepeatLayout.Flow; + lapps.RepeatDirection = RepeatDirection.Vertical; } protected override void OnPreRender(EventArgs e) @@ -366,23 +362,9 @@ namespace umbraco.cms.presentation.user ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/CMSNode.asmx")); // ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/legacyAjaxCalls.asmx")); - - + } - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - //lapps.SelectionMode = ListSelectionMode.Multiple; - lapps.RepeatLayout = RepeatLayout.Flow; - lapps.RepeatDirection = RepeatDirection.Vertical; - } - - #endregion - /// /// Handles the Click event of the saveUser control. /// diff --git a/src/umbraco.businesslogic/BasePages/UmbracoEnsuredPage.cs b/src/umbraco.businesslogic/BasePages/UmbracoEnsuredPage.cs index 9594b84b07..dd09265713 100644 --- a/src/umbraco.businesslogic/BasePages/UmbracoEnsuredPage.cs +++ b/src/umbraco.businesslogic/BasePages/UmbracoEnsuredPage.cs @@ -115,9 +115,6 @@ namespace umbraco.BasePages else Response.Redirect(SystemDirectories.Umbraco + "/logout.aspx?redir=" + Server.UrlEncode(Request.RawUrl), true); } - - System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(ui.Culture(this.getUser())); - System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture; } } } \ No newline at end of file diff --git a/src/umbraco.cms/businesslogic/ContentType.cs b/src/umbraco.cms/businesslogic/ContentType.cs index ebacf89fa1..71d676f54d 100644 --- a/src/umbraco.cms/businesslogic/ContentType.cs +++ b/src/umbraco.cms/businesslogic/ContentType.cs @@ -70,7 +70,7 @@ namespace umbraco.cms.businesslogic _alias = alias; _iconurl = icon; _thumbnail = thumbnail; - + if (masterContentType.HasValue) MasterContentType = masterContentType.Value; @@ -92,7 +92,7 @@ namespace umbraco.cms.businesslogic allowAtRoot, isContainer, Alias,icon,thumbnail,description FROM umbracoNode INNER JOIN cmsContentType ON umbracoNode.id = cmsContentType.nodeId WHERE nodeObjectType = @nodeObjectType"; - + #endregion #region Static Methods @@ -113,12 +113,12 @@ namespace umbraco.cms.businesslogic /// internal static IDictionary GetAliasesAndNames(string contentTypeAlias) { - return AliasToNames.GetOrAdd(contentTypeAlias, s => - { - var ct = GetByAlias(contentTypeAlias); - var userFields = ct.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name); - return userFields; - }); + return AliasToNames.GetOrAdd(contentTypeAlias, s => + { + var ct = GetByAlias(contentTypeAlias); + var userFields = ct.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name); + return userFields; + }); } /// @@ -150,21 +150,21 @@ namespace umbraco.cms.businesslogic //propertyTypeAlias needs to be invariant, so we will store uppercase var key = new System.Tuple(contentTypeAlias, propertyTypeAlias.ToUpper()); - + return PropertyTypeCache.GetOrAdd( key, tuple => - { - // With 4.10 we can't do this via direct SQL as we have content type mixins - var controlId = Guid.Empty; - var ct = GetByAlias(contentTypeAlias); + { + // With 4.10 we can't do this via direct SQL as we have content type mixins + var controlId = Guid.Empty; + var ct = GetByAlias(contentTypeAlias); var pt = ct.getPropertyType(propertyTypeAlias); if (pt != null) { controlId = pt.DataTypeDefinition.DataType.Id; } - return controlId; - }); + return controlId; + }); } /// @@ -244,7 +244,7 @@ namespace umbraco.cms.businesslogic private bool _isContainerContentType; private List _allowedChildContentTypeIDs; private List _virtualTabs; - + protected internal IContentTypeComposition ContentTypeItem; #endregion @@ -264,7 +264,7 @@ namespace umbraco.cms.businesslogic foreach (var i in GetContentIdsForContentType()) { RebuildXmlStructureForContentItem(i); - } + } } /// @@ -292,7 +292,7 @@ namespace umbraco.cms.businesslogic dr.Close(); } return ids; - } + } /// /// Rebuilds the xml structure for the content item by id @@ -325,7 +325,7 @@ namespace umbraco.cms.businesslogic INNER JOIN cmsContentType ON cmsContent.contentType = cmsContentType.nodeId WHERE cmsContentType.nodeId = @nodeId)", SqlHelper.CreateParameter("@nodeId", this.Id)); - } + } #endregion @@ -443,7 +443,7 @@ namespace umbraco.cms.businesslogic } } } - + /// /// Gets or sets the description. /// @@ -580,41 +580,41 @@ namespace umbraco.cms.businesslogic cacheKey, TimeSpan.FromMinutes(15), () => + { + //MCH NOTE: For the timing being I have changed this to a dictionary to ensure that property types + //aren't added multiple times through the MasterContentType structure, because each level loads + //its own + inherited property types, which is wrong. Once we are able to fully switch to the new api + //this should no longer be a problem as the composition always contains a correct list of property types. + var result = new Dictionary(); + using (IRecordsReader dr = + SqlHelper.ExecuteReader( + "select id from cmsPropertyType where contentTypeId = @ctId order by sortOrder", + SqlHelper.CreateParameter("@ctId", Id))) { - //MCH NOTE: For the timing being I have changed this to a dictionary to ensure that property types - //aren't added multiple times through the MasterContentType structure, because each level loads - //its own + inherited property types, which is wrong. Once we are able to fully switch to the new api - //this should no longer be a problem as the composition always contains a correct list of property types. - var result = new Dictionary(); - using (IRecordsReader dr = - SqlHelper.ExecuteReader( - "select id from cmsPropertyType where contentTypeId = @ctId order by sortOrder", - SqlHelper.CreateParameter("@ctId", Id))) + while (dr.Read()) { - while (dr.Read()) + int id = dr.GetInt("id"); + PropertyType pt = PropertyType.GetPropertyType(id); + if (pt != null) + result.Add(pt.Id, pt); + } + } + + // Get Property Types from the master content type + if (MasterContentTypes.Count > 0) + { + foreach (var mct in MasterContentTypes) + { + var pts = GetContentType(mct).PropertyTypes; + foreach (var pt in pts) { - int id = dr.GetInt("id"); - PropertyType pt = PropertyType.GetPropertyType(id); - if (pt != null) + if (result.ContainsKey(pt.Id) == false) result.Add(pt.Id, pt); } } - - // Get Property Types from the master content type - if (MasterContentTypes.Count > 0) - { - foreach (var mct in MasterContentTypes) - { - var pts = GetContentType(mct).PropertyTypes; - foreach (var pt in pts) - { - if (result.ContainsKey(pt.Id) == false) - result.Add(pt.Id, pt); - } - } - } - return result.Select(x => x.Value).ToList(); - }); + } + return result.Select(x => x.Value).ToList(); + }); } } @@ -850,7 +850,7 @@ namespace umbraco.cms.businesslogic foreach (var i in value) { int id = i; - list.Add(new ContentTypeSort{Id = new Lazy(() => id), SortOrder = sort}); + list.Add(new ContentTypeSort { Id = new Lazy(() => id), SortOrder = sort }); sort++; } @@ -963,7 +963,7 @@ namespace umbraco.cms.businesslogic { ContentTypeItem.RemovePropertyType(pt.Alias); } - + // Remove from cache FlushFromCache(Id); } @@ -1163,7 +1163,7 @@ namespace umbraco.cms.businesslogic /// /// The id. public static void FlushFromCache(int id) - { + { //Ensure that MediaTypes are reloaded from db by clearing cache InMemoryCacheProvider.Current.Clear(); @@ -1428,12 +1428,12 @@ namespace umbraco.cms.businesslogic public List GetAllPropertyTypes() { var db = ApplicationContext.Current.DatabaseContext.Database; - var propertyTypeDtos = db.Fetch("WHERE propertyTypeGroupId = @Id", new {Id = _id}); + var propertyTypeDtos = db.Fetch("WHERE propertyTypeGroupId = @Id", new { Id = _id }); var tmp = propertyTypeDtos .Select(propertyTypeDto => PropertyType.GetPropertyType(propertyTypeDto.Id)) .ToList(); - var propertyTypeGroupDtos = db.Fetch("WHERE parentGroupId = @Id", new {Id = _id}); + var propertyTypeGroupDtos = db.Fetch("WHERE parentGroupId = @Id", new { Id = _id }); foreach (var propertyTypeGroupDto in propertyTypeGroupDtos) { var inheritedPropertyTypeDtos = db.Fetch("WHERE propertyTypeGroupId = @Id", new { Id = propertyTypeGroupDto.Id }); @@ -1504,6 +1504,24 @@ namespace umbraco.cms.businesslogic } } + /// + /// Gets the tab caption by id. + /// + /// The id. + /// + internal static string GetRawCaptionById(int id) + { + try + { + var tempCaption = SqlHelper.ExecuteScalar("Select text from cmsPropertyTypeGroup where id = " + id); + return tempCaption; + } + catch + { + return null; + } + } + private readonly int _id; private int? _sortOrder; @@ -1629,7 +1647,7 @@ namespace umbraco.cms.businesslogic { if (!_caption.StartsWith("#")) return _caption; - + var lang = Language.GetByCultureCode(Thread.CurrentThread.CurrentCulture.Name); if (lang != null) { @@ -1645,6 +1663,5 @@ namespace umbraco.cms.businesslogic } } #endregion - } } diff --git a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs index 8c09d6db0c..876d05da06 100644 --- a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs +++ b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs @@ -15,7 +15,7 @@ namespace umbraco.cms.businesslogic.datatype /// Default implementation of the IData interface that stores data inside the Umbraco database. /// [Obsolete("This class is no longer used and will be removed from the codebase in the future.")] - public class DefaultData : IData, IDataWithPreview + public class DefaultData : IData, IDataWithPreview, IDataValueSetter { private int _propertyId; private object _value; @@ -58,10 +58,29 @@ namespace umbraco.cms.businesslogic.datatype _value = InitValue; } + /// + /// This is here for performance reasons since in some cases we will have already resolved the value from the db + /// and want to just give this object the value so it doesn't go re-look it up from the database. + /// + /// + /// + void IDataValueSetter.SetValue(object val, string strDbType) + { + _value = val; + //now that we've set our value, we can update our BaseDataType object with the correct values from the db + //instead of making it query for itself. This is a peformance optimization enhancement. + var dbType = BaseDataType.GetDBType(strDbType); + var fieldName = BaseDataType.GetDataFieldName(dbType); + _dataType.SetDataTypeProperties(fieldName, dbType); + + //ensures that it doesn't go back to the db + _valueLoaded = true; + } + /// /// Loads the data value from the database. /// - protected virtual void LoadValueFromDatabase() + protected internal virtual void LoadValueFromDatabase() { var sql = new Sql(); sql.Select("*") @@ -244,5 +263,7 @@ namespace umbraco.cms.businesslogic.datatype } #endregion + + } } diff --git a/src/umbraco.cms/businesslogic/web/DocumentType.cs b/src/umbraco.cms/businesslogic/web/DocumentType.cs index 1a9e203ada..31d5d89e14 100644 --- a/src/umbraco.cms/businesslogic/web/DocumentType.cs +++ b/src/umbraco.cms/businesslogic/web/DocumentType.cs @@ -527,7 +527,7 @@ namespace umbraco.cms.businesslogic.web //Datatype definition guid was added in v4 to enable datatype imports ptx.AppendChild(XmlHelper.AddTextNode(xd, "Definition", pt.DataTypeDefinition.UniqueId.ToString())); - ptx.AppendChild(XmlHelper.AddTextNode(xd, "Tab", Tab.GetCaptionById(pt.TabId))); + ptx.AppendChild(XmlHelper.AddTextNode(xd, "Tab", Tab.GetRawCaptionById(pt.TabId))); ptx.AppendChild(XmlHelper.AddTextNode(xd, "Mandatory", pt.Mandatory.ToString())); ptx.AppendChild(XmlHelper.AddTextNode(xd, "Validation", pt.ValidationRegExp)); ptx.AppendChild(XmlHelper.AddCDataNode(xd, "Description", pt.GetRawDescription())); diff --git a/src/umbraco.interfaces/IData.cs b/src/umbraco.interfaces/IData.cs index c0580f0fee..17a374ff05 100644 --- a/src/umbraco.interfaces/IData.cs +++ b/src/umbraco.interfaces/IData.cs @@ -3,6 +3,15 @@ using System.Xml; namespace umbraco.interfaces { + /// + /// Internal interface used to decorate any IData that can be optimized when exporting + /// XML like in the packaging service. Instead of relying on the IData to go get the value + /// from the db, any IData that implements this can have it's value set from the packaging service. + /// + internal interface IDataValueSetter + { + void SetValue(object val, string strDbType); + } /// /// The IData is part of the IDataType interface for creating new data types in the umbraco backoffice. diff --git a/src/umbraco.interfaces/Properties/AssemblyInfo.cs b/src/umbraco.interfaces/Properties/AssemblyInfo.cs index 06f4dbdf64..709f231e00 100644 --- a/src/umbraco.interfaces/Properties/AssemblyInfo.cs +++ b/src/umbraco.interfaces/Properties/AssemblyInfo.cs @@ -9,4 +9,11 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("umbraco.interfaces")] [assembly: AssemblyDescription("Core assembly containing legacy interfaces")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("Umbraco CMS")] \ No newline at end of file +[assembly: AssemblyProduct("Umbraco CMS")] + +[assembly: InternalsVisibleTo("cms")] +[assembly: InternalsVisibleTo("Umbraco.Core")] +[assembly: InternalsVisibleTo("Umbraco.Tests")] + +//allow this to be mocked in our unit tests +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]