From bd3ad153d29bbe7555c63a7b1bb9b5e0ed098578 Mon Sep 17 00:00:00 2001 From: Rasmus Fjord Date: Mon, 26 Sep 2016 11:05:15 +0200 Subject: [PATCH 01/88] bah --- .hgignore | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .hgignore diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000000..13307d790b --- /dev/null +++ b/.hgignore @@ -0,0 +1,58 @@ +syntax: glob +*.obj +*.exe +*.pdb +*.user +*.aps +*.pch +*.vspscc +[Bb]in +[Db]ebug*/ +obj/ +[Rr]elease*/ +_ReSharper*/ +*.ncrunchsolution +*.ncrunchsolution.user +*.ncrunchproject +*.crunchsolution.cache +[Tt]est[Rr]esult* +[Bb]uild[Ll]og.* +*.[Pp]ublish.xml +*.suo +[sS]ource +[sS]andbox +umbraco.config +*.vs10x +App_Data\TEMP\* +umbraco\presentation\umbraco\plugins\* +umbraco\presentation\usercontrols\* +umbraco\presentation\scripts\* +umbraco\presentation\fonts\* +umbraco\presentation\css\* + +src\Umbraco.Web.UI\css\* +src\Umbraco.Web.UI\App_Code\* +src\Umbraco.Web.UI\App_Data\* +src\Umbraco.Tests\App_Data\* +src\Umbraco.Web.UI\media\* +src\Umbraco.Web.UI\masterpages\* +src\Umbraco.Web.UI\macroScripts\* +src\Umbraco.Web.UI\xslt\* +umbraco\presentation\umbraco\plugins\uComponents\uComponentsInstaller.ascx +umbraco\presentation\packages\uComponents\MultiNodePicker\CustomTreeService.asmx +_BuildOutput/* +*.ncrunchsolution +build/UmbracoCms.AllBinaries.zip +build/UmbracoCms.WebPI.zip +build/UmbracoCms.zip +build/*.nupkg +src/Umbraco.Tests/config/applications.config +src/Umbraco.Tests/config/trees.config +src/Umbraco.Web.UI/web.config +*.orig +src/Umbraco.Tests/config/404handlers.config +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/* From f199b0a651dd430adf4916c16be255bdd8b54d4b Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Tue, 18 Oct 2016 22:35:46 +0200 Subject: [PATCH 02/88] Added default cachehelper to the relation repositories --- src/Umbraco.Core/Persistence/RepositoryFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 85eb00ea87..a960d46eeb 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -197,7 +197,7 @@ namespace Umbraco.Core.Persistence { return new RelationRepository( uow, - _noCache, //never cache + _cacheHelper, _logger, _sqlSyntax, CreateRelationTypeRepository(uow)); } @@ -206,7 +206,7 @@ namespace Umbraco.Core.Persistence { return new RelationTypeRepository( uow, - _noCache, //never cache + _cacheHelper, _logger, _sqlSyntax); } From 95af144ae9517bb3389406b660487e732cde2c67 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Oct 2016 13:08:35 +0200 Subject: [PATCH 03/88] Migrate locks to their own umbracoLocks table --- src/Umbraco.Core/Constants-System.cs | 3 -- src/Umbraco.Core/Models/Rdbms/LockDto.cs | 29 ++++++++++++++ .../Persistence/Constants-Locks.cs | 11 ++++++ .../Persistence/DatabasenodeLockExtensions.cs | 4 +- .../Migrations/Initial/BaseDataCreation.cs | 12 +++++- .../Initial/DatabaseSchemaCreation.cs | 3 +- .../AddLockObjects.cs | 39 +++++++++++++++++++ .../AddLockTable.cs | 32 +++++++++++++++ .../EnsureServersLockObject.cs | 2 +- .../AddServerRegistrationColumnsAndLock.cs | 2 +- .../Services/ServerRegistrationService.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 4 ++ 12 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 src/Umbraco.Core/Models/Rdbms/LockDto.cs create mode 100644 src/Umbraco.Core/Persistence/Constants-Locks.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockTable.cs diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index 4a30db9cd8..bc86d1717f 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -25,9 +25,6 @@ public const int DefaultContentListViewDataTypeId = -95; public const int DefaultMediaListViewDataTypeId = -96; public const int DefaultMembersListViewDataTypeId = -97; - - // identifiers for lock objects - public const int ServersLock = -331; } public static class DatabaseProviders diff --git a/src/Umbraco.Core/Models/Rdbms/LockDto.cs b/src/Umbraco.Core/Models/Rdbms/LockDto.cs new file mode 100644 index 0000000000..b543ce4241 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/LockDto.cs @@ -0,0 +1,29 @@ +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoLock")] + [PrimaryKey("id")] + [ExplicitColumns] + internal class LockDto + { + public LockDto() + { + Value = 1; + } + + [Column("id")] + [PrimaryKeyColumn(Name = "PK_umbracoLock")] + public int Id { get; set; } + + [Column("value")] + [NullSetting(NullSetting = NullSettings.NotNull)] + public int Value { get; set; } + + [Column("name")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Length(64)] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs new file mode 100644 index 0000000000..49c6f933fb --- /dev/null +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -0,0 +1,11 @@ +// ReSharper disable once CheckNamespace +namespace Umbraco.Core +{ + static partial class Constants + { + public static class Locks + { + public const int Servers = -331; + } + } +} diff --git a/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs b/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs index 3e6d245416..5ef29aa951 100644 --- a/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs +++ b/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence { ValidateDatabase(database); - database.Execute("UPDATE umbracoNode SET sortOrder = (CASE WHEN (sortOrder=1) THEN -1 ELSE 1 END) WHERE id=@id", + database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { @id = nodeId }); } @@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence { ValidateDatabase(database); - database.ExecuteScalar("SELECT sortOrder FROM umbracoNode WHERE id=@id", + database.ExecuteScalar("SELECT value FROM umbracoLock WHERE id=@id", new { @id = nodeId }); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 9570024b09..60fd37a357 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -33,7 +33,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial CreateUmbracNodeData(); } - if(tableName.Equals("cmsContentType")) + if (tableName.Equals("umbracoLock")) + { + CreateUmbracoLockData(); + } + + if (tableName.Equals("cmsContentType")) { CreateCmsContentTypeData(); } @@ -141,9 +146,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1038, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1038", SortOrder = 2, UniqueId = new Guid("1251c96c-185c-4e9b-93f4-b48205573cbd"), Text = "Simple Editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1042, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1042", SortOrder = 2, UniqueId = new Guid("0a452bd5-83f9-4bc3-8403-1286e13fb77e"), Text = "Macro Container", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + } + private void CreateUmbracoLockData() + { // all lock objects - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.ServersLock, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1," + Constants.System.ServersLock, SortOrder = 1, UniqueId = new Guid("0AF5E610-A310-4B6F-925F-E928D5416AF7"), Text = "LOCK: Servers", NodeObjectType = Constants.ObjectTypes.LockObjectGuid, CreateDate = DateTime.Now }); + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" }); } private void CreateCmsContentTypeData() diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 423c847c47..edac819474 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -84,7 +84,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial {45, typeof (MigrationDto)}, {46, typeof (UmbracoDeployChecksumDto)}, {47, typeof (UmbracoDeployDependencyDto)}, - {48, typeof (RedirectUrlDto) } + {48, typeof (RedirectUrlDto) }, + {49, typeof (LockDto) } }; #endregion diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs new file mode 100644 index 0000000000..d5c377fba1 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs @@ -0,0 +1,39 @@ +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive +{ + [Migration("7.5.5", 101, GlobalSettings.UmbracoMigrationName)] + public class AddLockObjects : MigrationBase + { + public AddLockObjects(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + EnsureLockObject(Constants.Locks.Servers, "Servers"); + } + + public override void Down() + { + // not implemented + } + + private void EnsureLockObject(int id, string name) + { + Execute.Code(db => + { + var exists = db.Exists(id); + if (exists) return string.Empty; + // be safe: delete old umbracoNode lock objects if any + db.Execute("DELETE FROM umbracoNode WHERE id=@id;", new { id }); + // then create umbracoLock object + db.Execute("INSERT umbracoLock (id, name, value) VALUES (@id, '@name', 1);", new { id, name }); + return string.Empty; + }); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockTable.cs new file mode 100644 index 0000000000..5dc1720a2a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockTable.cs @@ -0,0 +1,32 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive +{ + [Migration("7.5.5", 100, GlobalSettings.UmbracoMigrationName)] + public class AddLockTable : MigrationBase + { + public AddLockTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("umbracoLock") == false) + { + Create.Table("umbracoLock") + .WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock") + .WithColumn("value").AsInt32().NotNullable() + .WithColumn("name").AsString(64).NotNullable(); + } + } + + public override void Down() + { + // not implemented + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs index 6f9d74e5db..1bfef5ef6d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer // for some reason it was not, so it was created during migrations but not during // new installs, so for ppl that upgrade, make sure they have it - EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers"); + EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers"); } public override void Down() diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs index 118dc1fc06..6a14e408a9 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe Create.Column("isMaster").OnTable("umbracoServer").AsBoolean().NotNullable().WithDefaultValue(0); } - EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers"); + EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers"); } public override void Down() diff --git a/src/Umbraco.Core/Services/ServerRegistrationService.cs b/src/Umbraco.Core/Services/ServerRegistrationService.cs index 5a4a48b7aa..b2d5ca855e 100644 --- a/src/Umbraco.Core/Services/ServerRegistrationService.cs +++ b/src/Umbraco.Core/Services/ServerRegistrationService.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Services private readonly static string CurrentServerIdentityValue = NetworkHelper.MachineName // eg DOMAIN\SERVER + "/" + HttpRuntime.AppDomainAppId; // eg /LM/S3SVC/11/ROOT - private static readonly int[] LockingRepositoryIds = { Constants.System.ServersLock }; + private static readonly int[] LockingRepositoryIds = { Constants.Locks.Servers }; private ServerRole _currentServerRole = ServerRole.Unknown; private readonly LockingRepository _lrepo; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3dd960db06..7efbe99296 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -406,6 +406,7 @@ + @@ -415,6 +416,7 @@ + @@ -428,6 +430,8 @@ + + From c58cffe4a2dc32b99a1394776bd0c7e4b0d810fa Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Oct 2016 14:46:04 +0200 Subject: [PATCH 04/88] Add tests for locks --- src/Umbraco.Tests/Persistence/LocksTests.cs | 300 ++++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 2 files changed, 301 insertions(+) create mode 100644 src/Umbraco.Tests/Persistence/LocksTests.cs diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests/Persistence/LocksTests.cs new file mode 100644 index 0000000000..e973b14614 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/LocksTests.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlServerCe; +using System.Threading; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; +using Umbraco.Tests.Services; +using Umbraco.Tests.TestHelpers; +using Ignore = NUnit.Framework.IgnoreAttribute; + +namespace Umbraco.Tests.Persistence +{ + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + [Ignore("Takes too much time.")] + public class LocksTests : BaseDatabaseFactoryTest + { + private ThreadSafetyServiceTest.PerThreadPetaPocoUnitOfWorkProvider _uowProvider; + private ThreadSafetyServiceTest.PerThreadDatabaseFactory _dbFactory; + + [SetUp] + public override void Initialize() + { + base.Initialize(); + + //we need to use our own custom IDatabaseFactory for the DatabaseContext because we MUST ensure that + //a Database instance is created per thread, whereas the default implementation which will work in an HttpContext + //threading environment, or a single apartment threading environment will not work for this test because + //it is multi-threaded. + _dbFactory = new ThreadSafetyServiceTest.PerThreadDatabaseFactory(Logger); + //overwrite the local object + ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory, Logger, new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe); + + //disable cache + var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); + + //here we are going to override the ServiceContext because normally with our test cases we use a + //global Database object but this is NOT how it should work in the web world or in any multi threaded scenario. + //we need a new Database object for each thread. + var repositoryFactory = new RepositoryFactory(cacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings()); + _uowProvider = new ThreadSafetyServiceTest.PerThreadPetaPocoUnitOfWorkProvider(_dbFactory); + var evtMsgs = new TransientMessagesFactory(); + ApplicationContext.Services = new ServiceContext( + repositoryFactory, + _uowProvider, + new FileUnitOfWorkProvider(), + new PublishingStrategy(evtMsgs, Logger), + cacheHelper, + Logger, + evtMsgs); + + // create a few lock objects + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.Execute("SET IDENTITY_INSERT umbracoLock ON"); + database.Insert("umbracoLock", "id", false, new LockDto { Id = 1, Name = "Lock.1" }); + database.Insert("umbracoLock", "id", false, new LockDto { Id = 2, Name = "Lock.2" }); + database.Insert("umbracoLock", "id", false, new LockDto { Id = 3, Name = "Lock.3" }); + database.Execute("SET IDENTITY_INSERT umbracoLock OFF"); + database.CompleteTransaction(); + } + catch + { + database.AbortTransaction(); + } + } + + [TearDown] + public override void TearDown() + { + //dispose! + _dbFactory.Dispose(); + _uowProvider.Dispose(); + + base.TearDown(); + } + + [Test] + public void Test() + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeReadLock(Constants.Locks.Servers); + } + finally + { + database.CompleteTransaction(); + } + } + + [Test] + public void ConcurrentReadersTest() + { + var threads = new List(); + var locker = new object(); + var acquired = 0; + var maxAcquired = 0; + for (var i = 0; i < 5; i++) + { + threads.Add(new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeReadLock(Constants.Locks.Servers); + lock (locker) + { + acquired++; + } + Thread.Sleep(500); + lock (locker) + { + if (maxAcquired < acquired) maxAcquired = acquired; + } + Thread.Sleep(500); + lock (locker) + { + acquired--; + } + } + finally + { + database.CompleteTransaction(); + } + })); + } + foreach (var thread in threads) thread.Start(); + foreach (var thread in threads) thread.Join(); + Assert.AreEqual(5, maxAcquired); + } + + [Test] + public void ConcurrentWritersTest() + { + var threads = new List(); + var locker = new object(); + var acquired = 0; + var maxAcquired = 0; + for (var i = 0; i < 5; i++) + { + threads.Add(new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeWriteLock(Constants.Locks.Servers); + lock (locker) + { + acquired++; + } + Thread.Sleep(500); + lock (locker) + { + if (maxAcquired < acquired) maxAcquired = acquired; + } + Thread.Sleep(500); + lock (locker) + { + acquired--; + } + } + finally + { + database.CompleteTransaction(); + } + })); + } + foreach (var thread in threads) thread.Start(); + foreach (var thread in threads) thread.Join(); + Assert.AreEqual(1, maxAcquired); + } + + [Test] + public void DeadLockTest() + { + Exception e1 = null, e2 = null; + + var thread1 = new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeWriteLock(1); + Thread.Sleep(1000); + database.AcquireLockNodeWriteLock(2); + Thread.Sleep(1000); + } + catch (Exception e) + { + e1 = e; + } + finally + { + database.CompleteTransaction(); + } + }); + var thread2 = new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeWriteLock(2); + Thread.Sleep(1000); + database.AcquireLockNodeWriteLock(1); + Thread.Sleep(1000); + } + catch (Exception e) + { + e2 = e; + } + finally + { + database.CompleteTransaction(); + } + }); + thread1.Start(); + thread2.Start(); + thread1.Join(); + thread2.Join(); + Assert.IsNotNull(e1); + Assert.IsNotNull(e2); + Assert.IsInstanceOf(e1); + Assert.IsInstanceOf(e2); + } + + [Test] + public void NoDeadLockTest() + { + Exception e1 = null, e2 = null; + + var thread1 = new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeWriteLock(1); + var info = database.Query("SELECT * FROM sys.lock_information;"); + Console.WriteLine("LOCKS:"); + foreach (var row in info) + Console.WriteLine($"> {row.request_spid} {row.resource_type} {row.resource_description} {row.request_mode} {row.resource_table} {row.resource_table_id} {row.request_status}"); + Thread.Sleep(6000); + } + catch (Exception e) + { + e1 = e; + } + finally + { + database.CompleteTransaction(); + } + }); + var thread2 = new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + Thread.Sleep(1000); + database.AcquireLockNodeWriteLock(2); + var info = database.Query("SELECT * FROM sys.lock_information;"); + Console.WriteLine("LOCKS:"); + foreach (var row in info) + Console.WriteLine($"> {row.request_spid} {row.resource_type} {row.resource_description} {row.request_mode} {row.resource_table} {row.resource_table_id} {row.request_status}"); + Thread.Sleep(1000); + } + catch (Exception e) + { + e2 = e; + } + finally + { + database.CompleteTransaction(); + } + }); + thread1.Start(); + thread2.Start(); + thread1.Join(); + thread2.Join(); + Assert.IsNull(e1); + Assert.IsNull(e2); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 042fd6ff6f..25ed563e2d 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -176,6 +176,7 @@ + From 07bb57fac8d5721255b4585e7e196a55c4cc3e78 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 25 Oct 2016 12:22:06 +0200 Subject: [PATCH 05/88] U4-9018 - provide a way to disable server master election --- .../Sync/SingleServerRegistrar.cs | 37 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 2 files changed, 38 insertions(+) create mode 100644 src/Umbraco.Core/Sync/SingleServerRegistrar.cs diff --git a/src/Umbraco.Core/Sync/SingleServerRegistrar.cs b/src/Umbraco.Core/Sync/SingleServerRegistrar.cs new file mode 100644 index 0000000000..117ce516be --- /dev/null +++ b/src/Umbraco.Core/Sync/SingleServerRegistrar.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Sync +{ + public class SingleServerRegistrar : IServerRegistrar2 + { + private readonly string _umbracoApplicationUrl; + + public IEnumerable Registrations { get; private set; } + + public SingleServerRegistrar() + { + _umbracoApplicationUrl = ApplicationContext.Current.UmbracoApplicationUrl; + Registrations = new[] { new ServerAddressImpl(_umbracoApplicationUrl) }; + } + + public ServerRole GetCurrentServerRole() + { + return ServerRole.Single; + } + + public string GetCurrentServerUmbracoApplicationUrl() + { + return _umbracoApplicationUrl; + } + + private class ServerAddressImpl : IServerAddress + { + public ServerAddressImpl(string serverAddress) + { + ServerAddress = serverAddress; + } + + public string ServerAddress { get; private set; } + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index bf70f6afd6..e8ef0c64f2 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1330,6 +1330,7 @@ + From b617b9e39101b237cf425973fd6e1a99242213e0 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 25 Oct 2016 12:32:50 +0200 Subject: [PATCH 06/88] U4-9108 - add config setting --- src/Umbraco.Core/CoreBootManager.cs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 4da740f458..4ef08bd02f 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.IO; using System.Linq; using System.Threading; @@ -30,12 +31,13 @@ using Umbraco.Core.Manifest; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Core.Strings; +using IntegerValidator = Umbraco.Core.PropertyEditors.IntegerValidator; using MigrationsVersionFourNineZero = Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero; namespace Umbraco.Core { /// - /// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application + /// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application /// /// /// This does not provide any startup functionality relating to web objects @@ -191,14 +193,14 @@ namespace Umbraco.Core protected virtual CacheHelper CreateApplicationCache() { var cacheHelper = new CacheHelper( - //we need to have the dep clone runtime cache provider to ensure + //we need to have the dep clone runtime cache provider to ensure //all entities are cached properly (cloned in and cloned out) new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), new StaticCacheProvider(), //we have no request based cache when not running in web-based context new NullCacheProvider(), new IsolatedRuntimeCache(type => - //we need to have the dep clone runtime cache provider to ensure + //we need to have the dep clone runtime cache provider to ensure //all entities are cached properly (cloned in and cloned out) new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); @@ -251,18 +253,18 @@ namespace Umbraco.Core } /// - /// Special method to initialize the ApplicationEventsResolver and any modifications required for it such + /// Special method to initialize the ApplicationEventsResolver and any modifications required for it such /// as adding custom types to the resolver. /// protected virtual void InitializeApplicationEventsResolver() { //find and initialize the application startup handlers, we need to initialize this resolver here because - //it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to + //it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to //events and to call their events during bootup. //ApplicationStartupHandler.RegisterHandlers(); //... and set the special flag to let us resolve before frozen resolution ApplicationEventsResolver.Current = new ApplicationEventsResolver( - ServiceProvider, + ServiceProvider, ProfilingLogger.Logger, PluginManager.ResolveApplicationStartupHandlers()) { @@ -282,7 +284,7 @@ namespace Umbraco.Core } /// - /// Fires after initialization and calls the callback to allow for customizations to occur & + /// Fires after initialization and calls the callback to allow for customizations to occur & /// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called /// /// @@ -334,7 +336,7 @@ namespace Umbraco.Core { if (_isComplete) throw new InvalidOperationException("The boot manager has already been completed"); - + FreezeResolution(); //Here we need to make sure the db can be connected to @@ -366,7 +368,7 @@ namespace Umbraco.Core ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex); throw; } - }); + }); } //Now, startup all of our legacy startup handler @@ -455,6 +457,10 @@ namespace Umbraco.Core { ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar()); } + else if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"])) + { + ServerRegistrarResolver.Current = new ServerRegistrarResolver(new SingleServerRegistrar()); + } else { ServerRegistrarResolver.Current = new ServerRegistrarResolver( @@ -462,7 +468,6 @@ namespace Umbraco.Core new Lazy(() => ApplicationContext.Services.ServerRegistrationService), new DatabaseServerRegistrarOptions())); } - //by default we'll use the database server messenger with default options (no callbacks), // this will be overridden in the web startup @@ -473,7 +478,7 @@ namespace Umbraco.Core ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveAssignedMapperTypes()); - + //RepositoryResolver.Current = new RepositoryResolver( // new RepositoryFactory(ApplicationCache)); From 221354a7d8c2bcb41646c56fe3602e8a57b90152 Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 27 Oct 2016 10:47:32 +0200 Subject: [PATCH 07/88] U4-9104 Update the UmbracoExamine logic for Media to read the data directly from the umbracoXml table. updated some tests to use the testbehavior attribute so they dont rely on being run in a specific order. --- .../Interfaces/IMediaRepository.cs | 10 ++ .../Repositories/MediaRepository.cs | 24 ++++ src/Umbraco.Core/Services/IMediaService.cs | 14 ++ src/Umbraco.Core/Services/MediaService.cs | 21 +++ .../LegacyExamineBackedMediaTests.cs | 1 + .../UmbracoExamine/EventsTest.cs | 4 +- .../UmbracoExamine/IndexInitializer.cs | 60 ++++++-- src/Umbraco.Tests/UmbracoExamine/IndexTest.cs | 13 +- .../UmbracoExamine/SearchTests.cs | 2 + src/UmbracoExamine/UmbracoContentIndexer.cs | 130 +++++++++--------- 10 files changed, 193 insertions(+), 86 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index 907f9b62c5..64989f9269 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -38,5 +38,15 @@ namespace Umbraco.Core.Persistence.Repositories /// An Enumerable list of objects IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); + + /// + /// Gets paged media descendants as XML by path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of media items + IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 598c9e912d..7a6fa4c34e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -465,6 +465,30 @@ namespace Umbraco.Core.Persistence.Repositories } + /// + /// Gets paged media descendants as XML by path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of media items + public IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords) + { + Sql query; + if (path == "-1") + { + query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE nodeObjectType = @0)", Guid.Parse(Constants.ObjectTypes.Media)).OrderBy("nodeId"); + } + else + { + query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE path LIKE @0)", path.EnsureEndsWith(",%")).OrderBy("nodeId"); + } + var pagedResult = Database.Page(pageIndex+1, pageSize, query); + totalRecords = pagedResult.TotalItems; + return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml)); + } + private IEnumerable ProcessQuery(Sql sql) { //NOTE: This doesn't allow properties to be part of the query diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 6ff8f75402..d25ddf7f58 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Xml.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -57,6 +58,19 @@ namespace Umbraco.Core.Services /// public interface IMediaService : IService { + /// + /// Gets all XML entries found in the cmsContentXml table based on the given path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of media items + /// + /// If -1 is passed, then this will return all media xml entries, otherwise will return all descendents from the path + /// + IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords); + /// /// Rebuilds all xml content in the cmsContentXml table for all media /// diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 29235cc7ab..a1182ce80a 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -1199,6 +1199,27 @@ namespace Umbraco.Core.Services return true; } + /// + /// Gets paged media descendants as XML by path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of media items + public IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, out totalRecords); + return contents; + } + } + /// /// Rebuilds all xml content in the cmsContentXml table for all media /// diff --git a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs index 8a83bea75a..5bf6a4edc5 100644 --- a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Tests.PublishedContent { + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] public class LegacyExamineBackedMediaTests : ExamineBaseTest { public override void Initialize() diff --git a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs index 6bd01c7f1c..3e7377f3b6 100644 --- a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs @@ -3,11 +3,13 @@ using System.Linq; using Examine; using Lucene.Net.Store; using NUnit.Framework; +using Umbraco.Tests.TestHelpers; using UmbracoExamine; namespace Umbraco.Tests.UmbracoExamine { - [TestFixture] + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] public class EventsTest : ExamineBaseTest { [Test] diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index b303eed997..89a9df8052 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Xml.Linq; using Examine; using Examine.LuceneEngine.Config; using Examine.LuceneEngine.Providers; @@ -7,10 +9,14 @@ using Lucene.Net.Analysis; using Lucene.Net.Analysis.Standard; using Lucene.Net.Store; using Moq; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Services; using UmbracoExamine; using UmbracoExamine.Config; @@ -34,7 +40,8 @@ namespace Umbraco.Tests.UmbracoExamine IMediaService mediaService = null, IDataTypeService dataTypeService = null, IMemberService memberService = null, - IUserService userService = null) + IUserService userService = null, + IContentTypeService contentTypeService = null) { if (dataService == null) { @@ -94,7 +101,8 @@ namespace Umbraco.Tests.UmbracoExamine long longTotalRecs; int intTotalRecs; - var allRecs = dataService.MediaService.GetLatestMediaByXpath("//node") + var mediaXml = dataService.MediaService.GetLatestMediaByXpath("//node"); + var allRecs = mediaXml .Root .Elements() .Select(x => Mock.Of( @@ -114,20 +122,29 @@ namespace Umbraco.Tests.UmbracoExamine mt.Id == (int)x.Attribute("nodeType")))) .ToArray(); + // MOCK! + var mediaServiceMock = new Mock(); + + mediaServiceMock + .Setup(x => x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) + ).Returns(() => allRecs); + + mediaServiceMock + .Setup(x => x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + ).Returns(() => allRecs); + + mediaServiceMock + .Setup(x => x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out intTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + ).Returns(() => allRecs); + + mediaServiceMock.Setup(service => service.GetPagedXmlEntries(It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs)) + .Returns(() => allRecs.Select(x => x.ToXml())); + + mediaService = mediaServiceMock.Object; - mediaService = Mock.Of( - x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) - == - allRecs - && x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) - == - allRecs - && x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out intTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) - == - allRecs); } if (dataTypeService == null) { @@ -139,6 +156,18 @@ namespace Umbraco.Tests.UmbracoExamine memberService = Mock.Of(); } + if (contentTypeService == null) + { + var contentTypeServiceMock = new Mock(); + contentTypeServiceMock.Setup(x => x.GetAllContentTypes()) + .Returns(new List() + { + new ContentType(-1) {Alias = "Folder", Name = "Folder", Id = 1031, Icon = "icon-folder"}, + new ContentType(-1) {Alias = "Image", Name = "Image", Id = 1032, Icon = "icon-picture"} + }); + contentTypeService = contentTypeServiceMock.Object; + } + if (analyzer == null) { analyzer = new StandardAnalyzer(Version.LUCENE_29); @@ -154,6 +183,7 @@ namespace Umbraco.Tests.UmbracoExamine mediaService, dataTypeService, userService, + contentTypeService, analyzer, false); diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 7c36dd2953..3bc635bb23 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -10,15 +10,17 @@ using Lucene.Net.Index; using Lucene.Net.Search; using Lucene.Net.Store; using NUnit.Framework; +using Umbraco.Tests.TestHelpers; using UmbracoExamine; namespace Umbraco.Tests.UmbracoExamine { - /// - /// Tests the standard indexing capabilities - /// - [TestFixture, RequiresSTA] + /// + /// Tests the standard indexing capabilities + /// + //[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture, RequiresSTA] public class IndexTest : ExamineBaseTest { @@ -85,12 +87,11 @@ namespace Umbraco.Tests.UmbracoExamine //RESET the parent id existingCriteria = ((IndexCriteria)_indexer.IndexerData); _indexer.IndexerData = new IndexCriteria(existingCriteria.StandardFields, existingCriteria.UserFields, existingCriteria.IncludeNodeTypes, existingCriteria.ExcludeNodeTypes, - null); + null); //now ensure it's deleted var newResults = _searcher.Search(_searcher.CreateSearchCriteria().Id(2112).Compile()); Assert.AreEqual(1, newResults.Count()); - } [Test] diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index bcd9922e21..9b8b3d50d7 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -8,9 +8,11 @@ using Examine.LuceneEngine.Providers; using Lucene.Net.Store; using NUnit.Framework; using Examine.LuceneEngine.SearchCriteria; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.UmbracoExamine { + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] [TestFixture] public class SearchTests : ExamineBaseTest { diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index 4fcf51deae..ce3583a6be 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -1,20 +1,12 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Linq; -using System.Security; -using System.Text; -using System.Web; using System.Xml.Linq; using Examine; -using Examine.Config; -using Examine.Providers; using Lucene.Net.Documents; -using Lucene.Net.Index; using Umbraco.Core; -using umbraco.cms.businesslogic; using Umbraco.Core.Models; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Services; @@ -22,12 +14,9 @@ using UmbracoExamine.DataServices; using Examine.LuceneEngine; using Examine.LuceneEngine.Config; using UmbracoExamine.Config; -using Examine.LuceneEngine.Providers; using Lucene.Net.Analysis; -using umbraco.BasePages; using Umbraco.Core.Persistence.Querying; using IContentService = Umbraco.Core.Services.IContentService; -using UmbracoExamine.LocalStorage; using IMediaService = Umbraco.Core.Services.IMediaService; @@ -42,6 +31,7 @@ namespace UmbracoExamine private readonly IMediaService _mediaService; private readonly IDataTypeService _dataTypeService; private readonly IUserService _userService; + private readonly IContentTypeService _contentTypeService; #region Constructors @@ -55,6 +45,7 @@ namespace UmbracoExamine _mediaService = ApplicationContext.Current.Services.MediaService; _dataTypeService = ApplicationContext.Current.Services.DataTypeService; _userService = ApplicationContext.Current.Services.UserService; + _contentTypeService = ApplicationContext.Current.Services.ContentTypeService; } /// @@ -73,6 +64,7 @@ namespace UmbracoExamine _mediaService = ApplicationContext.Current.Services.MediaService; _dataTypeService = ApplicationContext.Current.Services.DataTypeService; _userService = ApplicationContext.Current.Services.UserService; + _contentTypeService = ApplicationContext.Current.Services.ContentTypeService; } /// @@ -91,6 +83,7 @@ namespace UmbracoExamine _mediaService = ApplicationContext.Current.Services.MediaService; _dataTypeService = ApplicationContext.Current.Services.DataTypeService; _userService = ApplicationContext.Current.Services.UserService; + _contentTypeService = ApplicationContext.Current.Services.ContentTypeService; } /// @@ -105,6 +98,7 @@ namespace UmbracoExamine /// /// /// + [Obsolete("Use the overload that specifies the Umbraco services")] public UmbracoContentIndexer(IIndexCriteria indexerData, Lucene.Net.Store.Directory luceneDirectory, IDataService dataService, IContentService contentService, IMediaService mediaService, @@ -117,13 +111,43 @@ namespace UmbracoExamine _mediaService = mediaService; _dataTypeService = dataTypeService; _userService = userService; + _contentTypeService = ApplicationContext.Current.Services.ContentTypeService; + } + + /// + /// Constructor to allow for creating an indexer at runtime + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public UmbracoContentIndexer(IIndexCriteria indexerData, Lucene.Net.Store.Directory luceneDirectory, IDataService dataService, + IContentService contentService, + IMediaService mediaService, + IDataTypeService dataTypeService, + IUserService userService, + IContentTypeService contentTypeService, + Analyzer analyzer, bool async) + : base(indexerData, luceneDirectory, dataService, analyzer, async) + { + _contentService = contentService; + _mediaService = mediaService; + _dataTypeService = dataTypeService; + _userService = userService; + _contentTypeService = contentTypeService; } #endregion #region Constants & Fields - + /// /// Used to store the path of a content object @@ -206,13 +230,8 @@ namespace UmbracoExamine SupportProtectedContent = supportProtected; else SupportProtectedContent = false; - - + base.Initialize(name, config); - - - - } #endregion @@ -285,10 +304,7 @@ namespace UmbracoExamine #endregion #region Public methods - - - /// /// Overridden for logging /// @@ -308,7 +324,6 @@ namespace UmbracoExamine { DataService.LogService.AddErrorLog(-1, string.Format("ReIndexNode cannot proceed, the format of the XElement is invalid, the xml has no 'id' attribute. {0}", node)); } - } /// @@ -355,8 +370,6 @@ namespace UmbracoExamine switch (type) { case IndexTypes.Content: - - var contentParentId = -1; if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0) { @@ -391,69 +404,63 @@ namespace UmbracoExamine { content = descendants.ToArray(); } - AddNodesToIndex(GetSerializedContent(content), type); pageIndex++; - - } while (content.Length == pageSize); break; case IndexTypes.Media: - var mediaParentId = -1; + if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0) { mediaParentId = IndexerData.ParentNodeId.Value; } - IMedia[] media; + XElement[] mediaXElements; + + var nodeTypes = _contentTypeService.GetAllContentTypes().ToArray(); + var icons = nodeTypes.ToDictionary(x => x.Id, y => y.Icon); + do { long total; - var descendants = _mediaService.GetPagedDescendants(mediaParentId, pageIndex, pageSize, out total); + if (mediaParentId == -1) + { + mediaXElements = _mediaService.GetPagedXmlEntries("-1", pageIndex, pageSize, out total).ToArray(); + } + else + { + //Get the parent + var parent = _mediaService.GetById(mediaParentId); + if (parent == null) + mediaXElements = new XElement[0]; + else + mediaXElements = _mediaService.GetPagedXmlEntries(parent.Path, pageIndex, pageSize, out total).ToArray(); + } //if specific types are declared we need to post filter them //TODO: Update the service layer to join the cmsContentType table so we can query by content type too if (IndexerData.IncludeNodeTypes.Any()) { - media = descendants.Where(x => IndexerData.IncludeNodeTypes.Contains(x.ContentType.Alias)).ToArray(); + var includeNodeTypeIds = nodeTypes.Where(x => IndexerData.IncludeNodeTypes.Contains(x.Alias)).Select(x => x.Id); + mediaXElements = mediaXElements.Where(elm => includeNodeTypeIds.Contains(elm.AttributeValue("nodeType"))).ToArray(); } - else + + // ReSharper disable once ForCanBeConvertedToForeach + for (var i = 0; i < mediaXElements.Length; i++) { - media = descendants.ToArray(); + mediaXElements[i].Add(new XAttribute("icon", icons[mediaXElements[i].AttributeValue("nodeType")])); } - - AddNodesToIndex(GetSerializedMedia(media), type); + + AddNodesToIndex(mediaXElements, type); pageIndex++; - } while (media.Length == pageSize); + } while (mediaXElements.Length == pageSize); break; } } - private IEnumerable GetSerializedMedia(IEnumerable media) - { - var serializer = new EntityXmlSerializer(); - foreach (var m in media) - { - var xml = serializer.Serialize( - _mediaService, - _dataTypeService, - _userService, - m); - - //add a custom 'icon' attribute - if (m.ContentType.Icon.IsNullOrWhiteSpace() == false) - { - xml.Add(new XAttribute("icon", m.ContentType.Icon)); - } - - - yield return xml; - } - } - private IEnumerable GetSerializedContent(IEnumerable content) { var serializer = new EntityXmlSerializer(); @@ -510,7 +517,6 @@ namespace UmbracoExamine protected override void OnGatheringNodeData(IndexingNodeDataEventArgs e) { - //strip html of all users fields if we detect it has HTML in it. //if that is the case, we'll create a duplicate 'raw' copy of it so that we can return //the value of the field 'as-is'. @@ -546,7 +552,6 @@ namespace UmbracoExamine var icon = (string)e.Node.Attribute("icon"); if (!e.Fields.ContainsKey(IconFieldName)) e.Fields.Add(IconFieldName, icon); - } /// @@ -584,7 +589,6 @@ namespace UmbracoExamine } return fields; - } /// @@ -605,7 +609,6 @@ namespace UmbracoExamine { return base.GetIndexerData(indexSet); } - } /// @@ -635,10 +638,9 @@ namespace UmbracoExamine { return false; } - return base.ValidateDocument(node); } #endregion } -} +} \ No newline at end of file From 563f8fbdca8703650c0e326649d804ce3de3a82c Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 27 Oct 2016 11:07:41 +0200 Subject: [PATCH 08/88] forgot one :) --- src/Umbraco.Tests/UmbracoExamine/IndexTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 3bc635bb23..5d6812f94a 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.UmbracoExamine /// /// Tests the standard indexing capabilities /// - //[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] [TestFixture, RequiresSTA] public class IndexTest : ExamineBaseTest { From 21e2c35ab0994db141ba8ef2173cf2fc4ce6cad2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 27 Oct 2016 17:36:08 +0200 Subject: [PATCH 09/88] U4-9111 Benchmark BulkCopy changes from U4-9107 --- .../Persistence/Mappers/BaseMapper.cs | 16 +- .../Persistence/Mappers/ContentMapper.cs | 6 + .../Querying/BaseExpressionHelper.cs | 426 ++++++++++++------ .../Persistence/Querying/IQuery.cs | 23 - .../Querying/ModelToSqlExpressionHelper.cs | 57 ++- .../Querying/PocoToSqlExpressionHelper.cs | 47 +- .../Persistence/Querying/Query.cs | 1 + .../Persistence/Querying/QueryExtensions.cs | 27 ++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../ModelToSqlExpressionHelperBenchmarks.cs | 105 +++++ src/Umbraco.Tests.Benchmarks/Program.cs | 3 +- .../Umbraco.Tests.Benchmarks.csproj | 6 +- .../Persistence/Querying/ExpressionTests.cs | 52 ++- 13 files changed, 554 insertions(+), 216 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Querying/QueryExtensions.cs create mode 100644 src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs diff --git a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs index ea7c8ab8f9..40ec415b30 100644 --- a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs @@ -8,7 +8,17 @@ namespace Umbraco.Core.Persistence.Mappers { public abstract class BaseMapper { - + private readonly ISqlSyntaxProvider _sqlSyntax; + + protected BaseMapper() : this(SqlSyntaxContext.SqlSyntaxProvider) + { + } + + protected BaseMapper(ISqlSyntaxProvider sqlSyntax) + { + _sqlSyntax = sqlSyntax; + } + internal abstract ConcurrentDictionary PropertyInfoCache { get; } internal abstract void BuildMap(); @@ -58,8 +68,8 @@ namespace Umbraco.Core.Persistence.Mappers string columnName = columnAttribute.Name; string columnMap = string.Format("{0}.{1}", - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(tableName), - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(columnName)); + _sqlSyntax.GetQuotedTableName(tableName), + _sqlSyntax.GetQuotedColumnName(columnName)); return columnMap; } } diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs index 1cc29cf959..5db0d1d3b0 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Linq.Expressions; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Mappers { @@ -16,6 +17,11 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public ContentMapper(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax) + { + + } + //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it // otherwise that would fail because there is no public constructor. public ContentMapper() diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 0960acc9e5..4bbd86d66b 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; @@ -10,22 +11,98 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { - internal abstract class BaseExpressionHelper : BaseExpressionHelper + + /// + /// This is used to determine if the expression result is cached and therefore only the SQL parameters will be extracted + /// + /// + /// This saves some performance overhead since the SQL string itself does not get generated because it already exists. + /// + internal class CachedExpression : Expression { + public CachedExpression() + { + CompiledOutput = null; + } + + public Expression InnerExpression { get; private set; } + + /// + /// The compiled SQL statement output + /// + public string CompiledOutput { get; private set; } + + public bool IsCompiled + { + get { return CompiledOutput.IsNullOrWhiteSpace() == false; } + } + + public void Compile(string output) + { + if (IsCompiled) + throw new InvalidOperationException("Cached expression is already compiled"); + + CompiledOutput = output; + } + + public void Wrap(Expression exp) + { + InnerExpression = exp; + } + } + + /// + /// An expression tree parser to create SQL statements and SQL parameters based on a given strongly typed expression + /// + /// + /// Logic that is shared with the expression helpers. This object stores state, it cannot be re-used to parse an expression. + /// + internal abstract class BaseExpressionHelper + { + protected BaseExpressionHelper(ISqlSyntaxProvider sqlSyntax) + { + SqlSyntax = sqlSyntax; + } + + /// + /// Indicates that the SQL statement has already been compiled, so Visiting will just generate the Sql Parameters + /// + protected bool IsCompiled { get; set; } + + protected ISqlSyntaxProvider SqlSyntax { get; private set; } + + protected List SqlParameters = new List(); + protected abstract string VisitMemberAccess(MemberExpression m); protected internal virtual string Visit(Expression exp) { + //set the flag if it is already compiled + var compiledExp = exp as CachedExpression; + if (compiledExp != null) + { + if (compiledExp.IsCompiled) + { + IsCompiled = true; + } + exp = compiledExp.InnerExpression; + } if (exp == null) return string.Empty; + + string result; + switch (exp.NodeType) { case ExpressionType.Lambda: - return VisitLambda(exp as LambdaExpression); + result = VisitLambda(exp as LambdaExpression); + break; case ExpressionType.MemberAccess: - return VisitMemberAccess(exp as MemberExpression); + result = VisitMemberAccess(exp as MemberExpression); + break; case ExpressionType.Constant: - return VisitConstant(exp as ConstantExpression); + result = VisitConstant(exp as ConstantExpression); + break; case ExpressionType.Add: case ExpressionType.AddChecked: case ExpressionType.Subtract: @@ -49,7 +126,8 @@ namespace Umbraco.Core.Persistence.Querying case ExpressionType.RightShift: case ExpressionType.LeftShift: case ExpressionType.ExclusiveOr: - return VisitBinary(exp as BinaryExpression); + result = VisitBinary(exp as BinaryExpression); + break; case ExpressionType.Negate: case ExpressionType.NegateChecked: case ExpressionType.Not: @@ -58,19 +136,37 @@ namespace Umbraco.Core.Persistence.Querying case ExpressionType.ArrayLength: case ExpressionType.Quote: case ExpressionType.TypeAs: - return VisitUnary(exp as UnaryExpression); + result = VisitUnary(exp as UnaryExpression); + break; case ExpressionType.Parameter: - return VisitParameter(exp as ParameterExpression); + result = VisitParameter(exp as ParameterExpression); + break; case ExpressionType.Call: - return VisitMethodCall(exp as MethodCallExpression); + result = VisitMethodCall(exp as MethodCallExpression); + break; case ExpressionType.New: - return VisitNew(exp as NewExpression); + result = VisitNew(exp as NewExpression); + break; case ExpressionType.NewArrayInit: case ExpressionType.NewArrayBounds: - return VisitNewArray(exp as NewArrayExpression); + result = VisitNewArray(exp as NewArrayExpression); + break; default: - return exp.ToString(); + result = exp.ToString(); + break; } + + if (compiledExp != null) + { + if (compiledExp.IsCompiled == false) + { + compiledExp.Compile(result); + } + return compiledExp.CompiledOutput; + } + + return result; + } protected virtual string VisitLambda(LambdaExpression lambda) @@ -79,14 +175,20 @@ namespace Umbraco.Core.Persistence.Querying { var m = lambda.Body as MemberExpression; - if (m.Expression != null) + if (m != null && m.Expression != null) { //This deals with members that are boolean (i.e. x => IsTrashed ) string r = VisitMemberAccess(m); - SqlParameters.Add(true); - return string.Format("{0} = @{1}", r, SqlParameters.Count - 1); - //return string.Format("{0}={1}", r, GetQuotedTrueValue()); + SqlParameters.Add(true); + + //don't execute if compiled + if (IsCompiled == false) + { + return string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + } + //already compiled, return + return string.Empty; } } @@ -95,8 +197,10 @@ namespace Umbraco.Core.Persistence.Querying protected virtual string VisitBinary(BinaryExpression b) { - string left, right; - var operand = BindOperant(b.NodeType); + var left = string.Empty; + var right = string.Empty; + + var operand = BindOperant(b.NodeType); if (operand == "AND" || operand == "OR") { MemberExpression m = b.Left as MemberExpression; @@ -105,9 +209,12 @@ namespace Umbraco.Core.Persistence.Querying string r = VisitMemberAccess(m); SqlParameters.Add(1); - left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); - //left = string.Format("{0}={1}", r, GetQuotedTrueValue()); + //don't execute if compiled + if (IsCompiled == false) + { + left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + } } else { @@ -119,9 +226,12 @@ namespace Umbraco.Core.Persistence.Querying string r = VisitMemberAccess(m); SqlParameters.Add(1); - right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); - //right = string.Format("{0}={1}", r, GetQuotedTrueValue()); + //don't execute if compiled + if (IsCompiled == false) + { + right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + } } else { @@ -132,14 +242,14 @@ namespace Umbraco.Core.Persistence.Querying { // deal with (x == true|false) - most common var constRight = b.Right as ConstantExpression; - if (constRight != null && constRight.Type == typeof (bool)) - return ((bool) constRight.Value) ? VisitNotNot(b.Left) : VisitNot(b.Left); + if (constRight != null && constRight.Type == typeof(bool)) + return ((bool)constRight.Value) ? VisitNotNot(b.Left) : VisitNot(b.Left); right = Visit(b.Right); // deal with (true|false == x) - why not var constLeft = b.Left as ConstantExpression; - if (constLeft != null && constLeft.Type == typeof (bool)) - return ((bool) constLeft.Value) ? VisitNotNot(b.Right) : VisitNot(b.Right); + if (constLeft != null && constLeft.Type == typeof(bool)) + return ((bool)constLeft.Value) ? VisitNotNot(b.Right) : VisitNot(b.Right); left = Visit(b.Left); } else if (operand == "<>") @@ -147,13 +257,13 @@ namespace Umbraco.Core.Persistence.Querying // deal with (x != true|false) - most common var constRight = b.Right as ConstantExpression; if (constRight != null && constRight.Type == typeof(bool)) - return ((bool) constRight.Value) ? VisitNot(b.Left) : VisitNotNot(b.Left); + return ((bool)constRight.Value) ? VisitNot(b.Left) : VisitNotNot(b.Left); right = Visit(b.Right); // deal with (true|false != x) - why not var constLeft = b.Left as ConstantExpression; if (constLeft != null && constLeft.Type == typeof(bool)) - return ((bool) constLeft.Value) ? VisitNot(b.Right) : VisitNotNot(b.Right); + return ((bool)constLeft.Value) ? VisitNot(b.Right) : VisitNotNot(b.Right); left = Visit(b.Left); } else @@ -178,9 +288,21 @@ namespace Umbraco.Core.Persistence.Querying { case "MOD": case "COALESCE": - return string.Format("{0}({1},{2})", operand, left, right); + //don't execute if compiled + if (IsCompiled == false) + { + return string.Format("{0}({1},{2})", operand, left, right); + } + //already compiled, return + return string.Empty; default: - return "(" + left + " " + operand + " " + right + ")"; + //don't execute if compiled + if (IsCompiled == false) + { + return string.Concat("(", left, " ", operand, " ", right, ")"); + } + //already compiled, return + return string.Empty; } } @@ -213,22 +335,33 @@ namespace Umbraco.Core.Persistence.Querying object o = getter(); SqlParameters.Add(o); - return string.Format("@{0}", SqlParameters.Count - 1); - //return GetQuotedValue(o, o.GetType()); + //don't execute if compiled + if (IsCompiled == false) + { + return string.Format("@{0}", SqlParameters.Count - 1); + } + //already compiled, return + return string.Empty; } catch (InvalidOperationException) - { - // FieldName ? - List exprs = VisitExpressionList(nex.Arguments); - var r = new StringBuilder(); - foreach (Object e in exprs) + { + //don't execute if compiled + if (IsCompiled == false) { - r.AppendFormat("{0}{1}", - r.Length > 0 ? "," : "", - e); + // FieldName ? + List exprs = VisitExpressionList(nex.Arguments); + var r = new StringBuilder(); + foreach (Object e in exprs) + { + r.AppendFormat("{0}{1}", + r.Length > 0 ? "," : "", + e); + } + return r.ToString(); } - return r.ToString(); + //already compiled, return + return string.Empty; } } @@ -244,14 +377,14 @@ namespace Umbraco.Core.Persistence.Querying return "null"; SqlParameters.Add(c.Value); - return string.Format("@{0}", SqlParameters.Count - 1); - //if (c.Value is bool) - //{ - // object o = GetQuotedValue(c.Value, c.Value.GetType()); - // return string.Format("({0}={1})", GetQuotedTrueValue(), o); - //} - //return GetQuotedValue(c.Value, c.Value.GetType()); + //don't execute if compiled + if (IsCompiled == false) + { + return string.Format("@{0}", SqlParameters.Count - 1); + } + //already compiled, return + return string.Empty; } protected virtual string VisitUnary(UnaryExpression u) @@ -277,10 +410,22 @@ namespace Umbraco.Core.Persistence.Querying case ExpressionType.MemberAccess: // false property , i.e. x => !Trashed SqlParameters.Add(true); - return string.Format("NOT ({0} = @{1})", o, SqlParameters.Count - 1); + //don't execute if compiled + if (IsCompiled == false) + { + return string.Format("NOT ({0} = @{1})", o, SqlParameters.Count - 1); + } + //already compiled, return + return string.Empty; default: - // could be anything else, such as: x => !x.Path.StartsWith("-20") - return "NOT (" + o + ")"; + //don't execute if compiled + if (IsCompiled == false) + { + // could be anything else, such as: x => !x.Path.StartsWith("-20") + return string.Concat("NOT (", o, ")"); + } + //already compiled, return + return string.Empty; } } @@ -293,7 +438,14 @@ namespace Umbraco.Core.Persistence.Querying case ExpressionType.MemberAccess: // true property, i.e. x => Trashed SqlParameters.Add(true); - return string.Format("({0} = @{1})", o, SqlParameters.Count - 1); + + //don't execute if compiled + if (IsCompiled == false) + { + return string.Format("({0} = @{1})", o, SqlParameters.Count - 1); + } + //already compiled, return + return string.Empty; default: // could be anything else, such as: x => x.Path.StartsWith("-20") return o; @@ -302,15 +454,22 @@ namespace Umbraco.Core.Persistence.Querying protected virtual string VisitNewArray(NewArrayExpression na) { - List exprs = VisitExpressionList(na.Expressions); - var r = new StringBuilder(); - foreach (Object e in exprs) - { - r.Append(r.Length > 0 ? "," + e : e); - } - return r.ToString(); + //don't execute if compiled + if (IsCompiled == false) + { + var r = new StringBuilder(); + foreach (Object e in exprs) + { + r.Append(r.Length > 0 ? "," + e : e); + } + + return r.ToString(); + } + //already compiled, return + return string.Empty; + } protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) @@ -375,19 +534,31 @@ namespace Umbraco.Core.Persistence.Querying var objectForMethod = m.Object ?? m.Arguments[0]; var visitedObjectForMethod = Visit(objectForMethod); - var methodArgs = m.Object == null - ? m.Arguments.Skip(1).ToArray() + var methodArgs = m.Object == null + ? m.Arguments.Skip(1).ToArray() : m.Arguments.ToArray(); switch (m.Method.Name) { case "ToString": SqlParameters.Add(objectForMethod.ToString()); - return string.Format("@{0}", SqlParameters.Count - 1); + //don't execute if compiled + if (IsCompiled == false) + return string.Format("@{0}", SqlParameters.Count - 1); + //already compiled, return + return string.Empty; case "ToUpper": - return string.Format("upper({0})", visitedObjectForMethod); + //don't execute if compiled + if (IsCompiled == false) + return string.Format("upper({0})", visitedObjectForMethod); + //already compiled, return + return string.Empty; case "ToLower": - return string.Format("lower({0})", visitedObjectForMethod); + //don't execute if compiled + if (IsCompiled == false) + return string.Format("lower({0})", visitedObjectForMethod); + //already compiled, return + return string.Empty; case "SqlWildcard": case "StartsWith": case "EndsWith": @@ -401,7 +572,7 @@ namespace Umbraco.Core.Persistence.Querying case "InvariantEndsWith": case "InvariantContains": case "InvariantEquals": - + string compareValue; if (methodArgs[0].NodeType != ExpressionType.Constant) @@ -488,7 +659,12 @@ namespace Umbraco.Core.Persistence.Querying SqlParameters.Add(RemoveQuote(replaceValue)); - return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); + //don't execute if compiled + if (IsCompiled == false) + return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); + //already compiled, return + return string.Empty; + //case "Substring": // var startIndex = Int32.Parse(args[0].ToString()) + 1; // if (args.Count == 2) @@ -555,77 +731,42 @@ namespace Umbraco.Core.Persistence.Querying throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); - //var s2 = new StringBuilder(); - //foreach (Object e in args) - //{ - // s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); - //} - //return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); + //var s2 = new StringBuilder(); + //foreach (Object e in args) + //{ + // s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); + //} + //return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); } } public virtual string GetQuotedTableName(string tableName) { + //already compiled, return + if (IsCompiled) + return tableName; + return string.Format("\"{0}\"", tableName); } public virtual string GetQuotedColumnName(string columnName) { + //already compiled, return + if (IsCompiled) + return columnName; + return string.Format("\"{0}\"", columnName); } public virtual string GetQuotedName(string name) { + //already compiled, return + if (IsCompiled) + return name; + return string.Format("\"{0}\"", name); } - //private string GetQuotedTrueValue() - //{ - // return GetQuotedValue(true, typeof(bool)); - //} - - //private string GetQuotedFalseValue() - //{ - // return GetQuotedValue(false, typeof(bool)); - //} - - //public virtual string GetQuotedValue(object value, Type fieldType) - //{ - // return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue); - //} - - //private string GetTrueExpression() - //{ - // object o = GetQuotedTrueValue(); - // return string.Format("({0}={1})", o, o); - //} - - //private string GetFalseExpression() - //{ - - // return string.Format("({0}={1})", - // GetQuotedTrueValue(), - // GetQuotedFalseValue()); - //} - - //private bool IsTrueExpression(string exp) - //{ - // return (exp == GetTrueExpression()); - //} - - //private bool IsFalseExpression(string exp) - //{ - // return (exp == GetFalseExpression()); - //} - } - - /// - /// Logic that is shared with the expression helpers - /// - internal class BaseExpressionHelper - { - protected List SqlParameters = new List(); - public object[] GetSqlParameters() { return SqlParameters.ToArray(); @@ -637,25 +778,45 @@ namespace Umbraco.Core.Persistence.Querying { case "SqlWildcard": SqlParameters.Add(RemoveQuote(val)); - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //don't execute if compiled + if (IsCompiled == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; case "Equals": SqlParameters.Add(RemoveQuote(val)); - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType); + //don't execute if compiled + if (IsCompiled == false) + return SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; case "StartsWith": SqlParameters.Add(string.Format("{0}{1}", RemoveQuote(val), - SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder())); - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + SqlSyntax.GetWildcardPlaceholder())); + //don't execute if compiled + if (IsCompiled == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; case "EndsWith": SqlParameters.Add(string.Format("{0}{1}", - SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(), + SqlSyntax.GetWildcardPlaceholder(), RemoveQuote(val))); - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //don't execute if compiled + if (IsCompiled == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; case "Contains": SqlParameters.Add(string.Format("{0}{1}{0}", - SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(), + SqlSyntax.GetWildcardPlaceholder(), RemoveQuote(val))); - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //don't execute if compiled + if (IsCompiled == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; case "InvariantEquals": case "SqlEquals": //recurse @@ -730,7 +891,7 @@ namespace Umbraco.Core.Persistence.Querying { return paramValue == null ? string.Empty - : SqlSyntaxContext.SqlSyntaxProvider.EscapeString(paramValue.ToString()); + : SqlSyntax.EscapeString(paramValue.ToString()); } public virtual bool ShouldQuoteValue(Type fieldType) @@ -740,16 +901,9 @@ namespace Umbraco.Core.Persistence.Querying protected virtual string RemoveQuote(string exp) { - //if (exp.StartsWith("'") && exp.EndsWith("'")) - //{ - // exp = exp.Remove(0, 1); - // exp = exp.Remove(exp.Length - 1, 1); - //} - //return exp; - if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) - && - (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) + && + (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) { exp = exp.Remove(0, 1); exp = exp.Remove(exp.Length - 1, 1); diff --git a/src/Umbraco.Core/Persistence/Querying/IQuery.cs b/src/Umbraco.Core/Persistence/Querying/IQuery.cs index b158943cb4..ae986baddc 100644 --- a/src/Umbraco.Core/Persistence/Querying/IQuery.cs +++ b/src/Umbraco.Core/Persistence/Querying/IQuery.cs @@ -1,31 +1,8 @@ using System; -using System.Collections.Generic; using System.Linq.Expressions; namespace Umbraco.Core.Persistence.Querying { - /// - /// SD: This is a horrible hack but unless we break compatibility with anyone who's actually implemented IQuery{T} there's not much we can do. - /// The IQuery{T} interface is useless without having a GetWhereClauses method and cannot be used for tests. - /// We have to wait till v8 to make this change I suppose. - /// - internal static class QueryExtensions - { - /// - /// Returns all translated where clauses and their sql parameters - /// - /// - public static IEnumerable> GetWhereClauses(this IQuery query) - { - var q = query as Query; - if (q == null) - { - throw new NotSupportedException(typeof(IQuery) + " cannot be cast to " + typeof(Query)); - } - return q.GetWhereClauses(); - } - } - /// /// Represents a query for building Linq translatable SQL queries /// diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs index a0ccfaa070..7e4466b529 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs @@ -11,14 +11,24 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { - internal class ModelToSqlExpressionHelper : BaseExpressionHelper + /// + /// An expression tree parser to create SQL statements and SQL parameters based on a given strongly typed expression based on Umbraco's business logic Models + /// + /// + /// This object stores state, it cannot be re-used to parse an expression + /// + internal class ModelToSqlExpressionHelper : BaseExpressionHelper { private readonly BaseMapper _mapper; - public ModelToSqlExpressionHelper() + public ModelToSqlExpressionHelper(ISqlSyntaxProvider sqlSyntax, BaseMapper mapper) : base(sqlSyntax) + { + _mapper = mapper; + } + + public ModelToSqlExpressionHelper() : this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T))) { - _mapper = MappingResolver.Current.ResolveMapperByType(typeof(T)); } protected override string VisitMemberAccess(MemberExpression m) @@ -27,18 +37,30 @@ namespace Umbraco.Core.Persistence.Querying m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof(T)) { - var field = _mapper.Map(m.Member.Name, true); - if (field.IsNullOrWhiteSpace()) - throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name); - return field; + //don't execute if compiled + if (IsCompiled == false) + { + var field = _mapper.Map(m.Member.Name, true); + if (field.IsNullOrWhiteSpace()) + throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name); + return field; + } + //already compiled, return + return string.Empty; } if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert) { - var field = _mapper.Map(m.Member.Name, true); - if (field.IsNullOrWhiteSpace()) - throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name); - return field; + //don't execute if compiled + if (IsCompiled == false) + { + var field = _mapper.Map(m.Member.Name, true); + if (field.IsNullOrWhiteSpace()) + throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name); + return field; + } + //already compiled, return + return string.Empty; } var member = Expression.Convert(m, typeof(object)); @@ -47,16 +69,13 @@ namespace Umbraco.Core.Persistence.Querying object o = getter(); SqlParameters.Add(o); - return string.Format("@{0}", SqlParameters.Count - 1); - //return GetQuotedValue(o, o != null ? o.GetType() : null); + //don't execute if compiled + if (IsCompiled == false) + return string.Format("@{0}", SqlParameters.Count - 1); + //already compiled, return + return string.Empty; } - - //protected bool IsFieldName(string quotedExp) - //{ - // //Not entirely sure this is reliable, but its better then simply returning true - // return quotedExp.LastIndexOf("'", StringComparison.InvariantCultureIgnoreCase) + 1 != quotedExp.Length; - //} } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs index bbdb7a5509..cbc3aa8f05 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs @@ -9,11 +9,18 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { - internal class PocoToSqlExpressionHelper : BaseExpressionHelper + + /// + /// An expression tree parser to create SQL statements and SQL parameters based on a given strongly typed expression based on Umbraco's PetaPoco dto Models + /// + /// + /// This object stores state, it cannot be re-used to parse an expression + /// + internal class PocoToSqlExpressionHelper : BaseExpressionHelper { private readonly Database.PocoData _pd; - public PocoToSqlExpressionHelper() + public PocoToSqlExpressionHelper() : base(SqlSyntaxContext.SqlSyntaxProvider) { _pd = new Database.PocoData(typeof(T)); } @@ -24,14 +31,26 @@ namespace Umbraco.Core.Persistence.Querying m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof(T)) { - string field = GetFieldName(_pd, m.Member.Name); - return field; + //don't execute if compiled + if (IsCompiled == false) + { + string field = GetFieldName(_pd, m.Member.Name); + return field; + } + //already compiled, return + return string.Empty; } if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert) { - string field = GetFieldName(_pd, m.Member.Name); - return field; + //don't execute if compiled + if (IsCompiled == false) + { + string field = GetFieldName(_pd, m.Member.Name); + return field; + } + //already compiled, return + return string.Empty; } var member = Expression.Convert(m, typeof(object)); @@ -40,23 +59,21 @@ namespace Umbraco.Core.Persistence.Querying object o = getter(); SqlParameters.Add(o); - return string.Format("@{0}", SqlParameters.Count - 1); - - //return GetQuotedValue(o, o != null ? o.GetType() : null); + //don't execute if compiled + if (IsCompiled == false) + return string.Format("@{0}", SqlParameters.Count - 1); + //already compiled, return + return string.Empty; } protected virtual string GetFieldName(Database.PocoData pocoData, string name) { var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name); return string.Format("{0}.{1}", - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(pocoData.TableInfo.TableName), - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(column.Value.ColumnName)); + SqlSyntax.GetQuotedTableName(pocoData.TableInfo.TableName), + SqlSyntax.GetQuotedColumnName(column.Value.ColumnName)); } - //protected bool IsFieldName(string quotedExp) - //{ - // return true; - //} } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/Query.cs b/src/Umbraco.Core/Persistence/Querying/Query.cs index 4dd268268f..1a270cec4b 100644 --- a/src/Umbraco.Core/Persistence/Querying/Query.cs +++ b/src/Umbraco.Core/Persistence/Querying/Query.cs @@ -30,6 +30,7 @@ namespace Umbraco.Core.Persistence.Querying { if (predicate != null) { + //TODO: This should have an SqlSyntax object passed in, this ctor is relying on a singleton var expressionHelper = new ModelToSqlExpressionHelper(); string whereExpression = expressionHelper.Visit(predicate); diff --git a/src/Umbraco.Core/Persistence/Querying/QueryExtensions.cs b/src/Umbraco.Core/Persistence/Querying/QueryExtensions.cs new file mode 100644 index 0000000000..20c3409a40 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/QueryExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// SD: This is a horrible hack but unless we break compatibility with anyone who's actually implemented IQuery{T} there's not much we can do. + /// The IQuery{T} interface is useless without having a GetWhereClauses method and cannot be used for tests. + /// We have to wait till v8 to make this change I suppose. + /// + internal static class QueryExtensions + { + /// + /// Returns all translated where clauses and their sql parameters + /// + /// + public static IEnumerable> GetWhereClauses(this IQuery query) + { + var q = query as Query; + if (q == null) + { + throw new NotSupportedException(typeof(IQuery) + " cannot be cast to " + typeof(Query)); + } + return q.GetWhereClauses(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c28cea24cd..36894801aa 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -474,6 +474,7 @@ + diff --git a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs new file mode 100644 index 0000000000..b492d73829 --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlServerCe; +using System.Diagnostics; +using System.IO; +using System.Linq.Expressions; +using System.Threading; +using System.Xml; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Diagnostics.Windows; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Validators; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.Migrations.Initial; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Tests.TestHelpers; +using ILogger = Umbraco.Core.Logging.ILogger; + +namespace Umbraco.Tests.Benchmarks +{ + [Config(typeof(Config))] + public class ModelToSqlExpressionHelperBenchmarks + { + private class Config : ManualConfig + { + public Config() + { + Add(new MemoryDiagnoser()); + } + } + + public ModelToSqlExpressionHelperBenchmarks() + { + _contentMapper = new ContentMapper(_syntaxProvider); + _contentMapper.BuildMap(); + _cachedExpression = new CachedExpression(); + } + + private readonly ISqlSyntaxProvider _syntaxProvider = new SqlCeSyntaxProvider(); + private readonly BaseMapper _contentMapper; + private readonly CachedExpression _cachedExpression; + //private static readonly Expression> TemplatePredicate = content => + // content.Path.StartsWith(string.Empty) && content.Published && (content.ContentTypeId == 0 || content.ContentTypeId == 0); + + [Benchmark(Baseline = true)] + public void WithNonCached() + { + for (int i = 0; i < 100; i++) + { + var a = i; + var b = i*10; + Expression> predicate = content => + content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b); + + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(_syntaxProvider, _contentMapper); + var result = modelToSqlExpressionHelper.Visit(predicate); + } + + } + + [Benchmark] + public void WithSQL() + { + var subQuery = new Sql() + //.Select("umbracoNode.id as nodeId") + .From(_syntaxProvider) + .InnerJoin(_syntaxProvider) + .On(_syntaxProvider, left => left.NodeId, right => right.NodeId) + .WhereIn(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + } + + [Benchmark()] + public void WithCachedExpression() + { + for (int i = 0; i < 100; i++) + { + var a = i; + var b = i * 10; + Expression> predicate = content => + content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b); + + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(_syntaxProvider, _contentMapper); + + //wrap it! + _cachedExpression.Wrap(predicate); + + var result = modelToSqlExpressionHelper.Visit(_cachedExpression); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests.Benchmarks/Program.cs b/src/Umbraco.Tests.Benchmarks/Program.cs index 37c1ccd853..eaac2abc0d 100644 --- a/src/Umbraco.Tests.Benchmarks/Program.cs +++ b/src/Umbraco.Tests.Benchmarks/Program.cs @@ -11,7 +11,8 @@ namespace Umbraco.Tests.Benchmarks { static void Main(string[] args) { - var summary = BenchmarkRunner.Run(); + var summary = BenchmarkRunner.Run(); + //var summary = BenchmarkRunner.Run(); Console.ReadLine(); } diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 66033ba08e..f6ef93fb20 100644 --- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -96,6 +96,7 @@ + @@ -103,16 +104,11 @@ - - - - - {31785bc3-256c-4613-b2f5-a1b0bdded8c1} diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index 4529343811..6834968460 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -16,19 +16,45 @@ namespace Umbraco.Tests.Persistence.Querying [TestFixture] public class ExpressionTests : BaseUsingSqlCeSyntax { - // [Test] - // public void Can_Query_With_Content_Type_Alias() - // { - // //Arrange - // Expression> predicate = content => content.ContentType.Alias == "Test"; - // var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); - // var result = modelToSqlExpressionHelper.Visit(predicate); + // [Test] + // public void Can_Query_With_Content_Type_Alias() + // { + // //Arrange + // Expression> predicate = content => content.ContentType.Alias == "Test"; + // var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + // var result = modelToSqlExpressionHelper.Visit(predicate); - // Debug.Print("Model to Sql ExpressionHelper: \n" + result); + // Debug.Print("Model to Sql ExpressionHelper: \n" + result); - // Assert.AreEqual("[cmsContentType].[alias] = @0", result); - // Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]); - // } + // Assert.AreEqual("[cmsContentType].[alias] = @0", result); + // Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]); + // } + + [Test] + public void CachedExpression_Can_Verify_Path_StartsWith_Predicate_In_Same_Result() + { + //Arrange + + //use a single cached expression for multiple expressions and ensure the correct output + // is done for both of them. + var cachedExpression = new CachedExpression(); + + + Expression> predicate1 = content => content.Path.StartsWith("-1"); + cachedExpression.Wrap(predicate1); + var modelToSqlExpressionHelper1 = new ModelToSqlExpressionHelper(); + var result1 = modelToSqlExpressionHelper1.Visit(cachedExpression); + Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result1); + Assert.AreEqual("-1%", modelToSqlExpressionHelper1.GetSqlParameters()[0]); + + Expression> predicate2 = content => content.Path.StartsWith("-1,123,97"); + cachedExpression.Wrap(predicate2); + var modelToSqlExpressionHelper2 = new ModelToSqlExpressionHelper(); + var result2 = modelToSqlExpressionHelper2.Visit(cachedExpression); + Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result2); + Assert.AreEqual("-1,123,97%", modelToSqlExpressionHelper2.GetSqlParameters()[0]); + + } [Test] public void Can_Verify_Path_StartsWith_Predicate_In_Same_Result() @@ -37,9 +63,7 @@ namespace Umbraco.Tests.Persistence.Querying Expression> predicate = content => content.Path.StartsWith("-1"); var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); var result = modelToSqlExpressionHelper.Visit(predicate); - - Debug.Print("Model to Sql ExpressionHelper: \n" + result); - + Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result); Assert.AreEqual("-1%", modelToSqlExpressionHelper.GetSqlParameters()[0]); } From 9717d03a75579457711552445ac676dec8ed466d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 27 Oct 2016 17:38:04 +0200 Subject: [PATCH 10/88] oops, didn't mean to commit this --- .../ModelToSqlExpressionHelperBenchmarks.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs index b492d73829..922badc33b 100644 --- a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs @@ -70,17 +70,7 @@ namespace Umbraco.Tests.Benchmarks } - [Benchmark] - public void WithSQL() - { - var subQuery = new Sql() - //.Select("umbracoNode.id as nodeId") - .From(_syntaxProvider) - .InnerJoin(_syntaxProvider) - .On(_syntaxProvider, left => left.NodeId, right => right.NodeId) - .WhereIn(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax) - .Where(x => x.NodeObjectType == NodeObjectTypeId); - } + [Benchmark()] public void WithCachedExpression() From 3f609fdf90c1625489ea70310d0999d5c99e57f6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 27 Oct 2016 17:50:52 +0200 Subject: [PATCH 11/88] Moves a few queries to re-used query instances --- .../Persistence/Repositories/ContentRepository.cs | 9 +++++++-- .../Persistence/Repositories/RepositoryBase.cs | 9 +++++++-- src/Umbraco.Core/Services/ContentService.cs | 9 +++++++-- src/Umbraco.Core/Services/EntityService.cs | 9 +++++++-- .../ModelToSqlExpressionHelperBenchmarks.cs | 4 +--- src/UmbracoExamine/UmbracoContentIndexer.cs | 9 ++++++--- 6 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 84caeb2a3e..d989edebb8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -104,6 +104,12 @@ namespace Umbraco.Core.Persistence.Repositories #endregion + #region Static Queries + + private readonly IQuery _publishedQuery = Query.Builder.Where(x => x.Published == true); + + #endregion + #region Overrides of PetaPocoRepositoryBase @@ -207,8 +213,7 @@ namespace Umbraco.Core.Persistence.Repositories //now insert the data, again if something fails here, the whole transaction is reversed if (contentTypeIds == null) { - var query = Query.Builder.Where(x => x.Published == true); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + RebuildXmlStructuresProcessQuery(serializer, _publishedQuery, tr, groupSize); } else { diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 5534a9ea40..9379d14af8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -83,6 +83,12 @@ namespace Umbraco.Core.Persistence.Repositories } + #region Static Queries + + private readonly IQuery _hasIdQuery = Query.Builder.Where(x => x.Id != 0); + + #endregion + protected virtual TId GetEntityId(TEntity entity) { return (TId)(object)entity.Id; @@ -112,8 +118,7 @@ namespace Umbraco.Core.Persistence.Repositories new RepositoryCachePolicyOptions(() => { //Get count of all entities of current type (TEntity) to ensure cached result is correct - var query = Query.Builder.Where(x => x.Id != 0); - return PerformCount(query); + return PerformCount(_hasIdQuery); }))); } } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 38b7aec9d7..6223e5933b 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -56,6 +56,12 @@ namespace Umbraco.Core.Services _userService = userService; } + #region Static Queries + + private readonly IQuery _notTrashedQuery = Query.Builder.Where(x => x.Trashed == false); + + #endregion + public int CountPublished(string contentTypeAlias = null) { var uow = UowProvider.GetUnitOfWork(); @@ -789,8 +795,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Trashed == false); - return repository.GetByPublishedVersion(query); + return repository.GetByPublishedVersion(_notTrashedQuery); } } diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 257416d054..a6b84704fc 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -62,6 +62,12 @@ namespace Umbraco.Core.Services } + #region Static Queries + + private readonly IQuery _rootEntityQuery = Query.Builder.Where(x => x.ParentId == -1); + + #endregion + /// /// Returns the integer id for a given GUID /// @@ -389,8 +395,7 @@ namespace Umbraco.Core.Services var objectTypeId = umbracoObjectType.GetGuid(); using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.ParentId == -1); - var entities = repository.GetByQuery(query, objectTypeId); + var entities = repository.GetByQuery(_rootEntityQuery, objectTypeId); return entities; } diff --git a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs index 922badc33b..2b1561012b 100644 --- a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs @@ -51,9 +51,7 @@ namespace Umbraco.Tests.Benchmarks private readonly ISqlSyntaxProvider _syntaxProvider = new SqlCeSyntaxProvider(); private readonly BaseMapper _contentMapper; private readonly CachedExpression _cachedExpression; - //private static readonly Expression> TemplatePredicate = content => - // content.Path.StartsWith(string.Empty) && content.Published && (content.ContentTypeId == 0 || content.ContentTypeId == 0); - + [Benchmark(Baseline = true)] public void WithNonCached() { diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index 4fcf51deae..f69e7e59bb 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -347,6 +347,11 @@ namespace UmbracoExamine #region Protected + /// + /// This is a static query, it's parameters don't change so store statically + /// + private static readonly IQuery PublishedQuery = Query.Builder.Where(x => x.Published == true); + protected override void PerformIndexAll(string type) { const int pageSize = 10000; @@ -376,9 +381,7 @@ namespace UmbracoExamine else { //add the published filter - var qry = Query.Builder.Where(x => x.Published == true); - - descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "Path", Direction.Ascending, true, qry); + descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "Path", Direction.Ascending, true, PublishedQuery); } //if specific types are declared we need to post filter them From e2d8a2808708138a4b9949d259eec418e2813da7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 27 Oct 2016 18:45:42 +0200 Subject: [PATCH 12/88] ensures the generic base class static query instance is lazily created --- src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs | 2 +- .../Persistence/Repositories/RepositoryBase.cs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs index ea44964219..6909c77744 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs @@ -43,7 +43,7 @@ namespace Umbraco.Core.Persistence.Mappers { return byAttribute.Result; } - throw new Exception("Invalid Type: A Mapper could not be resolved based on the passed in Type"); + throw new Exception("Invalid Type: A Mapper could not be resolved based on the passed in Type " + type); }); } diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 9379d14af8..41946d48d4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -85,7 +85,7 @@ namespace Umbraco.Core.Persistence.Repositories #region Static Queries - private readonly IQuery _hasIdQuery = Query.Builder.Where(x => x.Id != 0); + private IQuery _hasIdQuery; #endregion @@ -117,6 +117,12 @@ namespace Umbraco.Core.Persistence.Repositories RuntimeCache, new RepositoryCachePolicyOptions(() => { + //create it once if it is needed (no need for locking here) + if (_hasIdQuery == null) + { + _hasIdQuery = Query.Builder.Where(x => x.Id != 0); + } + //Get count of all entities of current type (TEntity) to ensure cached result is correct return PerformCount(_hasIdQuery); }))); From 958392f09b193a972e071033207574dc94387c97 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 28 Oct 2016 16:30:20 +0200 Subject: [PATCH 13/88] U4-9105 - bit of cleanup --- .../Persistence/PetaPocoSqlExtensions.cs | 2 +- ...sionHelper.cs => ExpressionVisitorBase.cs} | 1841 ++++++++--------- ...lper.cs => ModelToSqlExpressionVisitor.cs} | 153 +- ...elper.cs => PocoToSqlExpressionVisitor.cs} | 150 +- .../Persistence/Querying/Query.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 6 +- .../ModelToSqlExpressionHelperBenchmarks.cs | 4 +- .../Persistence/Querying/ExpressionTests.cs | 20 +- 8 files changed, 1077 insertions(+), 1101 deletions(-) rename src/Umbraco.Core/Persistence/Querying/{BaseExpressionHelper.cs => ExpressionVisitorBase.cs} (81%) rename src/Umbraco.Core/Persistence/Querying/{ModelToSqlExpressionHelper.cs => ModelToSqlExpressionVisitor.cs} (65%) rename src/Umbraco.Core/Persistence/Querying/{PocoToSqlExpressionHelper.cs => PocoToSqlExpressionVisitor.cs} (73%) diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs index f5c0e0e616..d2fa98ef12 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence public static Sql Where(this Sql sql, Expression> predicate) { - var expresionist = new PocoToSqlExpressionHelper(); + var expresionist = new PocoToSqlExpressionVisitor(); var whereExpression = expresionist.Visit(predicate); return sql.Where(whereExpression, expresionist.GetSqlParameters()); } diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs similarity index 81% rename from src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs rename to src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs index 4bbd86d66b..678ceb1d8e 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs @@ -1,927 +1,916 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Querying -{ - - /// - /// This is used to determine if the expression result is cached and therefore only the SQL parameters will be extracted - /// - /// - /// This saves some performance overhead since the SQL string itself does not get generated because it already exists. - /// - internal class CachedExpression : Expression - { - public CachedExpression() - { - CompiledOutput = null; - } - - public Expression InnerExpression { get; private set; } - - /// - /// The compiled SQL statement output - /// - public string CompiledOutput { get; private set; } - - public bool IsCompiled - { - get { return CompiledOutput.IsNullOrWhiteSpace() == false; } - } - - public void Compile(string output) - { - if (IsCompiled) - throw new InvalidOperationException("Cached expression is already compiled"); - - CompiledOutput = output; - } - - public void Wrap(Expression exp) - { - InnerExpression = exp; - } - } - - /// - /// An expression tree parser to create SQL statements and SQL parameters based on a given strongly typed expression - /// - /// - /// Logic that is shared with the expression helpers. This object stores state, it cannot be re-used to parse an expression. - /// - internal abstract class BaseExpressionHelper - { - protected BaseExpressionHelper(ISqlSyntaxProvider sqlSyntax) - { - SqlSyntax = sqlSyntax; - } - - /// - /// Indicates that the SQL statement has already been compiled, so Visiting will just generate the Sql Parameters - /// - protected bool IsCompiled { get; set; } - - protected ISqlSyntaxProvider SqlSyntax { get; private set; } - - protected List SqlParameters = new List(); - - protected abstract string VisitMemberAccess(MemberExpression m); - - protected internal virtual string Visit(Expression exp) - { - //set the flag if it is already compiled - var compiledExp = exp as CachedExpression; - if (compiledExp != null) - { - if (compiledExp.IsCompiled) - { - IsCompiled = true; - } - exp = compiledExp.InnerExpression; - } - - if (exp == null) return string.Empty; - - string result; - - switch (exp.NodeType) - { - case ExpressionType.Lambda: - result = VisitLambda(exp as LambdaExpression); - break; - case ExpressionType.MemberAccess: - result = VisitMemberAccess(exp as MemberExpression); - break; - case ExpressionType.Constant: - result = VisitConstant(exp as ConstantExpression); - break; - case ExpressionType.Add: - case ExpressionType.AddChecked: - case ExpressionType.Subtract: - case ExpressionType.SubtractChecked: - case ExpressionType.Multiply: - case ExpressionType.MultiplyChecked: - case ExpressionType.Divide: - case ExpressionType.Modulo: - case ExpressionType.And: - case ExpressionType.AndAlso: - case ExpressionType.Or: - case ExpressionType.OrElse: - case ExpressionType.LessThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.GreaterThan: - case ExpressionType.GreaterThanOrEqual: - case ExpressionType.Equal: - case ExpressionType.NotEqual: - case ExpressionType.Coalesce: - case ExpressionType.ArrayIndex: - case ExpressionType.RightShift: - case ExpressionType.LeftShift: - case ExpressionType.ExclusiveOr: - result = VisitBinary(exp as BinaryExpression); - break; - case ExpressionType.Negate: - case ExpressionType.NegateChecked: - case ExpressionType.Not: - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - case ExpressionType.ArrayLength: - case ExpressionType.Quote: - case ExpressionType.TypeAs: - result = VisitUnary(exp as UnaryExpression); - break; - case ExpressionType.Parameter: - result = VisitParameter(exp as ParameterExpression); - break; - case ExpressionType.Call: - result = VisitMethodCall(exp as MethodCallExpression); - break; - case ExpressionType.New: - result = VisitNew(exp as NewExpression); - break; - case ExpressionType.NewArrayInit: - case ExpressionType.NewArrayBounds: - result = VisitNewArray(exp as NewArrayExpression); - break; - default: - result = exp.ToString(); - break; - } - - if (compiledExp != null) - { - if (compiledExp.IsCompiled == false) - { - compiledExp.Compile(result); - } - return compiledExp.CompiledOutput; - } - - return result; - - } - - protected virtual string VisitLambda(LambdaExpression lambda) - { - if (lambda.Body.NodeType == ExpressionType.MemberAccess) - { - var m = lambda.Body as MemberExpression; - - if (m != null && m.Expression != null) - { - //This deals with members that are boolean (i.e. x => IsTrashed ) - string r = VisitMemberAccess(m); - - SqlParameters.Add(true); - - //don't execute if compiled - if (IsCompiled == false) - { - return string.Format("{0} = @{1}", r, SqlParameters.Count - 1); - } - //already compiled, return - return string.Empty; - } - - } - return Visit(lambda.Body); - } - - protected virtual string VisitBinary(BinaryExpression b) - { - var left = string.Empty; - var right = string.Empty; - - var operand = BindOperant(b.NodeType); - if (operand == "AND" || operand == "OR") - { - MemberExpression m = b.Left as MemberExpression; - if (m != null && m.Expression != null) - { - string r = VisitMemberAccess(m); - - SqlParameters.Add(1); - - //don't execute if compiled - if (IsCompiled == false) - { - left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); - } - } - else - { - left = Visit(b.Left); - } - m = b.Right as MemberExpression; - if (m != null && m.Expression != null) - { - string r = VisitMemberAccess(m); - - SqlParameters.Add(1); - - //don't execute if compiled - if (IsCompiled == false) - { - right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); - } - } - else - { - right = Visit(b.Right); - } - } - else if (operand == "=") - { - // deal with (x == true|false) - most common - var constRight = b.Right as ConstantExpression; - if (constRight != null && constRight.Type == typeof(bool)) - return ((bool)constRight.Value) ? VisitNotNot(b.Left) : VisitNot(b.Left); - right = Visit(b.Right); - - // deal with (true|false == x) - why not - var constLeft = b.Left as ConstantExpression; - if (constLeft != null && constLeft.Type == typeof(bool)) - return ((bool)constLeft.Value) ? VisitNotNot(b.Right) : VisitNot(b.Right); - left = Visit(b.Left); - } - else if (operand == "<>") - { - // deal with (x != true|false) - most common - var constRight = b.Right as ConstantExpression; - if (constRight != null && constRight.Type == typeof(bool)) - return ((bool)constRight.Value) ? VisitNot(b.Left) : VisitNotNot(b.Left); - right = Visit(b.Right); - - // deal with (true|false != x) - why not - var constLeft = b.Left as ConstantExpression; - if (constLeft != null && constLeft.Type == typeof(bool)) - return ((bool)constLeft.Value) ? VisitNot(b.Right) : VisitNotNot(b.Right); - left = Visit(b.Left); - } - else - { - left = Visit(b.Left); - right = Visit(b.Right); - } - - if (operand == "=" && right == "null") operand = "is"; - else if (operand == "<>" && right == "null") operand = "is not"; - else if (operand == "=" || operand == "<>") - { - //if (IsTrueExpression(right)) right = GetQuotedTrueValue(); - //else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); - - //if (IsTrueExpression(left)) left = GetQuotedTrueValue(); - //else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); - - } - - switch (operand) - { - case "MOD": - case "COALESCE": - //don't execute if compiled - if (IsCompiled == false) - { - return string.Format("{0}({1},{2})", operand, left, right); - } - //already compiled, return - return string.Empty; - default: - //don't execute if compiled - if (IsCompiled == false) - { - return string.Concat("(", left, " ", operand, " ", right, ")"); - } - //already compiled, return - return string.Empty; - } - } - - protected virtual List VisitExpressionList(ReadOnlyCollection original) - { - var list = new List(); - for (int i = 0, n = original.Count; i < n; i++) - { - if (original[i].NodeType == ExpressionType.NewArrayInit || - original[i].NodeType == ExpressionType.NewArrayBounds) - { - - list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); - } - else - list.Add(Visit(original[i])); - - } - return list; - } - - protected virtual string VisitNew(NewExpression nex) - { - // TODO : check ! - var member = Expression.Convert(nex, typeof(object)); - var lambda = Expression.Lambda>(member); - try - { - var getter = lambda.Compile(); - object o = getter(); - - SqlParameters.Add(o); - - //don't execute if compiled - if (IsCompiled == false) - { - return string.Format("@{0}", SqlParameters.Count - 1); - } - //already compiled, return - return string.Empty; - } - catch (InvalidOperationException) - { - //don't execute if compiled - if (IsCompiled == false) - { - // FieldName ? - List exprs = VisitExpressionList(nex.Arguments); - var r = new StringBuilder(); - foreach (Object e in exprs) - { - r.AppendFormat("{0}{1}", - r.Length > 0 ? "," : "", - e); - } - return r.ToString(); - } - //already compiled, return - return string.Empty; - } - - } - - protected virtual string VisitParameter(ParameterExpression p) - { - return p.Name; - } - - protected virtual string VisitConstant(ConstantExpression c) - { - if (c.Value == null) - return "null"; - - SqlParameters.Add(c.Value); - - //don't execute if compiled - if (IsCompiled == false) - { - return string.Format("@{0}", SqlParameters.Count - 1); - } - //already compiled, return - return string.Empty; - } - - protected virtual string VisitUnary(UnaryExpression u) - { - switch (u.NodeType) - { - case ExpressionType.Not: - return VisitNot(u.Operand); - default: - return Visit(u.Operand); - } - } - - private string VisitNot(Expression exp) - { - var o = Visit(exp); - - // use a "NOT (...)" syntax instead of "<>" since we don't know whether "<>" works in all sql servers - // also, x.StartsWith(...) translates to "x LIKE '...%'" which we cannot "<>" and have to "NOT (...") - - switch (exp.NodeType) - { - case ExpressionType.MemberAccess: - // false property , i.e. x => !Trashed - SqlParameters.Add(true); - //don't execute if compiled - if (IsCompiled == false) - { - return string.Format("NOT ({0} = @{1})", o, SqlParameters.Count - 1); - } - //already compiled, return - return string.Empty; - default: - //don't execute if compiled - if (IsCompiled == false) - { - // could be anything else, such as: x => !x.Path.StartsWith("-20") - return string.Concat("NOT (", o, ")"); - } - //already compiled, return - return string.Empty; - } - } - - private string VisitNotNot(Expression exp) - { - var o = Visit(exp); - - switch (exp.NodeType) - { - case ExpressionType.MemberAccess: - // true property, i.e. x => Trashed - SqlParameters.Add(true); - - //don't execute if compiled - if (IsCompiled == false) - { - return string.Format("({0} = @{1})", o, SqlParameters.Count - 1); - } - //already compiled, return - return string.Empty; - default: - // could be anything else, such as: x => x.Path.StartsWith("-20") - return o; - } - } - - protected virtual string VisitNewArray(NewArrayExpression na) - { - List exprs = VisitExpressionList(na.Expressions); - - //don't execute if compiled - if (IsCompiled == false) - { - var r = new StringBuilder(); - foreach (Object e in exprs) - { - r.Append(r.Length > 0 ? "," + e : e); - } - - return r.ToString(); - } - //already compiled, return - return string.Empty; - - } - - protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) - { - - List exprs = VisitExpressionList(na.Expressions); - return exprs; - } - - protected virtual string BindOperant(ExpressionType e) - { - - switch (e) - { - case ExpressionType.Equal: - return "="; - case ExpressionType.NotEqual: - return "<>"; - case ExpressionType.GreaterThan: - return ">"; - case ExpressionType.GreaterThanOrEqual: - return ">="; - case ExpressionType.LessThan: - return "<"; - case ExpressionType.LessThanOrEqual: - return "<="; - case ExpressionType.AndAlso: - return "AND"; - case ExpressionType.OrElse: - return "OR"; - case ExpressionType.Add: - return "+"; - case ExpressionType.Subtract: - return "-"; - case ExpressionType.Multiply: - return "*"; - case ExpressionType.Divide: - return "/"; - case ExpressionType.Modulo: - return "MOD"; - case ExpressionType.Coalesce: - return "COALESCE"; - default: - return e.ToString(); - } - } - - protected virtual string VisitMethodCall(MethodCallExpression m) - { - //Here's what happens with a MethodCallExpression: - // If a method is called that contains a single argument, - // then m.Object is the object on the left hand side of the method call, example: - // x.Path.StartsWith(content.Path) - // m.Object = x.Path - // and m.Arguments.Length == 1, therefor m.Arguments[0] == content.Path - // If a method is called that contains multiple arguments, then m.Object == null and the - // m.Arguments collection contains the left hand side of the method call, example: - // x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar) - // m.Object == null - // m.Arguments.Length == 3, therefor, m.Arguments[0] == x.Path, m.Arguments[1] == content.Path, m.Arguments[2] == TextColumnType.NVarchar - // So, we need to cater for these scenarios. - - var objectForMethod = m.Object ?? m.Arguments[0]; - var visitedObjectForMethod = Visit(objectForMethod); - var methodArgs = m.Object == null - ? m.Arguments.Skip(1).ToArray() - : m.Arguments.ToArray(); - - switch (m.Method.Name) - { - case "ToString": - SqlParameters.Add(objectForMethod.ToString()); - //don't execute if compiled - if (IsCompiled == false) - return string.Format("@{0}", SqlParameters.Count - 1); - //already compiled, return - return string.Empty; - case "ToUpper": - //don't execute if compiled - if (IsCompiled == false) - return string.Format("upper({0})", visitedObjectForMethod); - //already compiled, return - return string.Empty; - case "ToLower": - //don't execute if compiled - if (IsCompiled == false) - return string.Format("lower({0})", visitedObjectForMethod); - //already compiled, return - return string.Empty; - case "SqlWildcard": - case "StartsWith": - case "EndsWith": - case "Contains": - case "Equals": - case "SqlStartsWith": - case "SqlEndsWith": - case "SqlContains": - case "SqlEquals": - case "InvariantStartsWith": - case "InvariantEndsWith": - case "InvariantContains": - case "InvariantEquals": - - string compareValue; - - if (methodArgs[0].NodeType != ExpressionType.Constant) - { - //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) - // So we'll go get the value: - var member = Expression.Convert(methodArgs[0], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - compareValue = getter().ToString(); - } - else - { - compareValue = methodArgs[0].ToString(); - } - - //special case, if it is 'Contains' and the member that Contains is being called on is not a string, then - // we should be doing an 'In' clause - but we currently do not support this - if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) - { - throw new NotSupportedException("An array Contains method is not supported"); - } - - //default column type - var colType = TextColumnType.NVarchar; - - //then check if the col type argument has been passed to the current method (this will be the case for methods like - // SqlContains and other Sql methods) - if (methodArgs.Length > 1) - { - var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); - if (colTypeArg != null) - { - colType = (TextColumnType)((ConstantExpression)colTypeArg).Value; - } - } - - return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); - - case "Replace": - string searchValue; - - if (methodArgs[0].NodeType != ExpressionType.Constant) - { - //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) - // So we'll go get the value: - var member = Expression.Convert(methodArgs[0], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - searchValue = getter().ToString(); - } - else - { - searchValue = methodArgs[0].ToString(); - } - - if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) - { - throw new NotSupportedException("An array Contains method is not supported"); - } - - string replaceValue; - - if (methodArgs[1].NodeType != ExpressionType.Constant) - { - //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) - // So we'll go get the value: - var member = Expression.Convert(methodArgs[1], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - replaceValue = getter().ToString(); - } - else - { - replaceValue = methodArgs[1].ToString(); - } - - if (methodArgs[1].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[1].Type)) - { - throw new NotSupportedException("An array Contains method is not supported"); - } - - SqlParameters.Add(RemoveQuote(searchValue)); - - SqlParameters.Add(RemoveQuote(replaceValue)); - - //don't execute if compiled - if (IsCompiled == false) - return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); - //already compiled, return - return string.Empty; - - //case "Substring": - // var startIndex = Int32.Parse(args[0].ToString()) + 1; - // if (args.Count == 2) - // { - // var length = Int32.Parse(args[1].ToString()); - // return string.Format("substring({0} from {1} for {2})", - // r, - // startIndex, - // length); - // } - // else - // return string.Format("substring({0} from {1})", - // r, - // startIndex); - //case "Round": - //case "Floor": - //case "Ceiling": - //case "Coalesce": - //case "Abs": - //case "Sum": - // return string.Format("{0}({1}{2})", - // m.Method.Name, - // r, - // args.Count == 1 ? string.Format(",{0}", args[0]) : ""); - //case "Concat": - // var s = new StringBuilder(); - // foreach (Object e in args) - // { - // s.AppendFormat(" || {0}", e); - // } - // return string.Format("{0}{1}", r, s); - - //case "In": - - // var member = Expression.Convert(m.Arguments[0], typeof(object)); - // var lambda = Expression.Lambda>(member); - // var getter = lambda.Compile(); - - // var inArgs = (object[])getter(); - - // var sIn = new StringBuilder(); - // foreach (var e in inArgs) - // { - // SqlParameters.Add(e); - - // sIn.AppendFormat("{0}{1}", - // sIn.Length > 0 ? "," : "", - // string.Format("@{0}", SqlParameters.Count - 1)); - - // //sIn.AppendFormat("{0}{1}", - // // sIn.Length > 0 ? "," : "", - // // GetQuotedValue(e, e.GetType())); - // } - - // return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString()); - //case "Desc": - // return string.Format("{0} DESC", r); - //case "Alias": - //case "As": - // return string.Format("{0} As {1}", r, - // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); - - default: - - throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); - - //var s2 = new StringBuilder(); - //foreach (Object e in args) - //{ - // s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); - //} - //return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); - } - } - - public virtual string GetQuotedTableName(string tableName) - { - //already compiled, return - if (IsCompiled) - return tableName; - - return string.Format("\"{0}\"", tableName); - } - - public virtual string GetQuotedColumnName(string columnName) - { - //already compiled, return - if (IsCompiled) - return columnName; - - return string.Format("\"{0}\"", columnName); - } - - public virtual string GetQuotedName(string name) - { - //already compiled, return - if (IsCompiled) - return name; - - return string.Format("\"{0}\"", name); - } - - public object[] GetSqlParameters() - { - return SqlParameters.ToArray(); - } - - protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType) - { - switch (verb) - { - case "SqlWildcard": - SqlParameters.Add(RemoveQuote(val)); - //don't execute if compiled - if (IsCompiled == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "Equals": - SqlParameters.Add(RemoveQuote(val)); - //don't execute if compiled - if (IsCompiled == false) - return SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "StartsWith": - SqlParameters.Add(string.Format("{0}{1}", - RemoveQuote(val), - SqlSyntax.GetWildcardPlaceholder())); - //don't execute if compiled - if (IsCompiled == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "EndsWith": - SqlParameters.Add(string.Format("{0}{1}", - SqlSyntax.GetWildcardPlaceholder(), - RemoveQuote(val))); - //don't execute if compiled - if (IsCompiled == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "Contains": - SqlParameters.Add(string.Format("{0}{1}{0}", - SqlSyntax.GetWildcardPlaceholder(), - RemoveQuote(val))); - //don't execute if compiled - if (IsCompiled == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "InvariantEquals": - case "SqlEquals": - //recurse - return HandleStringComparison(col, val, "Equals", columnType); - case "InvariantStartsWith": - case "SqlStartsWith": - //recurse - return HandleStringComparison(col, val, "StartsWith", columnType); - case "InvariantEndsWith": - case "SqlEndsWith": - //recurse - return HandleStringComparison(col, val, "EndsWith", columnType); - case "InvariantContains": - case "SqlContains": - //recurse - return HandleStringComparison(col, val, "Contains", columnType); - default: - throw new ArgumentOutOfRangeException("verb"); - } - } - - //public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null) - //{ - // if (value == null) return "NULL"; - - // if (escapeCallback == null) - // { - // escapeCallback = EscapeParam; - // } - // if (shouldQuoteCallback == null) - // { - // shouldQuoteCallback = ShouldQuoteValue; - // } - - // if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string)) - // { - // //if (TypeSerializer.CanCreateFromString(fieldType)) - // //{ - // // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'"; - // //} - - // throw new NotSupportedException( - // string.Format("Property of type: {0} is not supported", fieldType.FullName)); - // } - - // if (fieldType == typeof(int)) - // return ((int)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(float)) - // return ((float)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(double)) - // return ((double)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(decimal)) - // return ((decimal)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(DateTime)) - // { - // return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'"; - // } - - // if (fieldType == typeof(bool)) - // return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture); - - // return shouldQuoteCallback(fieldType) - // ? "'" + escapeCallback(value) + "'" - // : value.ToString(); - //} - - public virtual string EscapeParam(object paramValue) - { - return paramValue == null - ? string.Empty - : SqlSyntax.EscapeString(paramValue.ToString()); - } - - public virtual bool ShouldQuoteValue(Type fieldType) - { - return true; - } - - protected virtual string RemoveQuote(string exp) - { - if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) - && - (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) - { - exp = exp.Remove(0, 1); - exp = exp.Remove(exp.Length - 1, 1); - } - return exp; - } - - //protected virtual string RemoveQuoteFromAlias(string exp) - //{ - - // if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) - // && - // (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) - // { - // exp = exp.Remove(0, 1); - // exp = exp.Remove(exp.Length - 1, 1); - // } - // return exp; - //} - } +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// Represents an expression which caches the visitor's result. + /// + internal class CachedExpression : Expression + { + private string _visitResult; + + /// + /// Gets or sets the inner Expression. + /// + public Expression InnerExpression { get; private set; } + + /// + /// Gets or sets the compiled SQL statement output. + /// + public string VisitResult + { + get { return _visitResult; } + set + { + if (Visited) + throw new InvalidOperationException("Cached expression has already been visited."); + _visitResult = value; + Visited = true; + } + } + + /// + /// Gets or sets a value indicating whether the cache Expression has been compiled already. + /// + public bool Visited { get; private set; } + + /// + /// Replaces the inner expression. + /// + /// expression. + /// The new expression is assumed to have different parameter but produce the same SQL statement. + public void Wrap(Expression expression) + { + InnerExpression = expression; + } + } + + /// + /// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression. + /// + /// This object is stateful and cannot be re-used to parse an expression. + internal abstract class ExpressionVisitorBase + { + protected ExpressionVisitorBase(ISqlSyntaxProvider sqlSyntax) + { + SqlSyntax = sqlSyntax; + } + + /// + /// Gets or sets a value indicating whether the visited expression has been visited already, + /// in which case visiting will just populate the SQL parameters. + /// + protected bool Visited { get; set; } + + /// + /// Gets or sets the SQL syntax provider for the current database. + /// + protected ISqlSyntaxProvider SqlSyntax { get; private set; } + + /// + /// Gets the list of SQL parameters. + /// + protected readonly List SqlParameters = new List(); + + /// + /// Gets the SQL parameters. + /// + /// + public object[] GetSqlParameters() + { + return SqlParameters.ToArray(); + } + + /// + /// Visits the expression and produces the corresponding SQL statement. + /// + /// The expression + /// The SQL statement corresponding to the expression. + /// Also populates the SQL parameters. + public virtual string Visit(Expression expression) + { + // if the expression is a CachedExpression, + // visit the inner expression if not already visited + var cachedExpression = expression as CachedExpression; + if (cachedExpression != null) + { + Visited = cachedExpression.Visited; + expression = cachedExpression.InnerExpression; + } + + if (expression == null) return string.Empty; + + string result; + + switch (expression.NodeType) + { + case ExpressionType.Lambda: + result = VisitLambda(expression as LambdaExpression); + break; + case ExpressionType.MemberAccess: + result = VisitMemberAccess(expression as MemberExpression); + break; + case ExpressionType.Constant: + result = VisitConstant(expression as ConstantExpression); + break; + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.Or: + case ExpressionType.OrElse: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.Coalesce: + case ExpressionType.ArrayIndex: + case ExpressionType.RightShift: + case ExpressionType.LeftShift: + case ExpressionType.ExclusiveOr: + result = VisitBinary(expression as BinaryExpression); + break; + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.ArrayLength: + case ExpressionType.Quote: + case ExpressionType.TypeAs: + result = VisitUnary(expression as UnaryExpression); + break; + case ExpressionType.Parameter: + result = VisitParameter(expression as ParameterExpression); + break; + case ExpressionType.Call: + result = VisitMethodCall(expression as MethodCallExpression); + break; + case ExpressionType.New: + result = VisitNew(expression as NewExpression); + break; + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + result = VisitNewArray(expression as NewArrayExpression); + break; + default: + result = expression.ToString(); + break; + } + + // if the expression is a CachedExpression, + // and is not already compiled, assign the result + if (cachedExpression != null) + { + if (cachedExpression.Visited == false) + cachedExpression.VisitResult = result; + result = cachedExpression.VisitResult; + } + + return result; + } + + protected abstract string VisitMemberAccess(MemberExpression m); + + protected virtual string VisitLambda(LambdaExpression lambda) + { + if (lambda.Body.NodeType == ExpressionType.MemberAccess) + { + var m = lambda.Body as MemberExpression; + + if (m != null && m.Expression != null) + { + //This deals with members that are boolean (i.e. x => IsTrashed ) + var r = VisitMemberAccess(m); + + SqlParameters.Add(true); + + return Visited ? string.Empty : string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + } + + } + return Visit(lambda.Body); + } + + protected virtual string VisitBinary(BinaryExpression b) + { + var left = string.Empty; + var right = string.Empty; + + var operand = BindOperant(b.NodeType); + if (operand == "AND" || operand == "OR") + { + var m = b.Left as MemberExpression; + if (m != null && m.Expression != null) + { + string r = VisitMemberAccess(m); + + SqlParameters.Add(1); + + //don't execute if compiled + if (Visited == false) + { + left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + } + } + else + { + left = Visit(b.Left); + } + m = b.Right as MemberExpression; + if (m != null && m.Expression != null) + { + var r = VisitMemberAccess(m); + + SqlParameters.Add(1); + + //don't execute if compiled + if (Visited == false) + { + right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + } + } + else + { + right = Visit(b.Right); + } + } + else if (operand == "=") + { + // deal with (x == true|false) - most common + var constRight = b.Right as ConstantExpression; + if (constRight != null && constRight.Type == typeof(bool)) + return (bool)constRight.Value ? VisitNotNot(b.Left) : VisitNot(b.Left); + right = Visit(b.Right); + + // deal with (true|false == x) - why not + var constLeft = b.Left as ConstantExpression; + if (constLeft != null && constLeft.Type == typeof(bool)) + return (bool)constLeft.Value ? VisitNotNot(b.Right) : VisitNot(b.Right); + left = Visit(b.Left); + } + else if (operand == "<>") + { + // deal with (x != true|false) - most common + var constRight = b.Right as ConstantExpression; + if (constRight != null && constRight.Type == typeof(bool)) + return (bool)constRight.Value ? VisitNot(b.Left) : VisitNotNot(b.Left); + right = Visit(b.Right); + + // deal with (true|false != x) - why not + var constLeft = b.Left as ConstantExpression; + if (constLeft != null && constLeft.Type == typeof(bool)) + return (bool)constLeft.Value ? VisitNot(b.Right) : VisitNotNot(b.Right); + left = Visit(b.Left); + } + else + { + left = Visit(b.Left); + right = Visit(b.Right); + } + + if (operand == "=" && right == "null") operand = "is"; + else if (operand == "<>" && right == "null") operand = "is not"; + else if (operand == "=" || operand == "<>") + { + //if (IsTrueExpression(right)) right = GetQuotedTrueValue(); + //else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); + + //if (IsTrueExpression(left)) left = GetQuotedTrueValue(); + //else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); + + } + + switch (operand) + { + case "MOD": + case "COALESCE": + //don't execute if compiled + if (Visited == false) + { + return string.Format("{0}({1},{2})", operand, left, right); + } + //already compiled, return + return string.Empty; + default: + //don't execute if compiled + if (Visited == false) + { + return string.Concat("(", left, " ", operand, " ", right, ")"); + } + //already compiled, return + return string.Empty; + } + } + + protected virtual List VisitExpressionList(ReadOnlyCollection original) + { + var list = new List(); + for (int i = 0, n = original.Count; i < n; i++) + { + if (original[i].NodeType == ExpressionType.NewArrayInit || + original[i].NodeType == ExpressionType.NewArrayBounds) + { + list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); + } + else + { + list.Add(Visit(original[i])); + } + } + return list; + } + + protected virtual string VisitNew(NewExpression nex) + { + // TODO : check ! + var member = Expression.Convert(nex, typeof(object)); + var lambda = Expression.Lambda>(member); + try + { + var getter = lambda.Compile(); + var o = getter(); + + SqlParameters.Add(o); + + //don't execute if compiled + if (Visited == false) + { + return string.Format("@{0}", SqlParameters.Count - 1); + } + //already compiled, return + return string.Empty; + } + catch (InvalidOperationException) + { + //don't execute if compiled + if (Visited == false) + { + // FieldName ? + List exprs = VisitExpressionList(nex.Arguments); + var r = new StringBuilder(); + foreach (var e in exprs) + { + r.AppendFormat("{0}{1}", + r.Length > 0 ? "," : "", + e); + } + return r.ToString(); + } + //already compiled, return + return string.Empty; + } + } + + protected virtual string VisitParameter(ParameterExpression p) + { + return p.Name; + } + + protected virtual string VisitConstant(ConstantExpression c) + { + if (c.Value == null) + return "null"; + + SqlParameters.Add(c.Value); + + //don't execute if compiled + if (Visited == false) + { + return string.Format("@{0}", SqlParameters.Count - 1); + } + //already compiled, return + return string.Empty; + } + + protected virtual string VisitUnary(UnaryExpression u) + { + switch (u.NodeType) + { + case ExpressionType.Not: + return VisitNot(u.Operand); + default: + return Visit(u.Operand); + } + } + + private string VisitNot(Expression exp) + { + var o = Visit(exp); + + // use a "NOT (...)" syntax instead of "<>" since we don't know whether "<>" works in all sql servers + // also, x.StartsWith(...) translates to "x LIKE '...%'" which we cannot "<>" and have to "NOT (...") + + switch (exp.NodeType) + { + case ExpressionType.MemberAccess: + // false property , i.e. x => !Trashed + SqlParameters.Add(true); + //don't execute if compiled + if (Visited == false) + { + return string.Format("NOT ({0} = @{1})", o, SqlParameters.Count - 1); + } + //already compiled, return + return string.Empty; + default: + //don't execute if compiled + if (Visited == false) + { + // could be anything else, such as: x => !x.Path.StartsWith("-20") + return string.Concat("NOT (", o, ")"); + } + //already compiled, return + return string.Empty; + } + } + + private string VisitNotNot(Expression exp) + { + var o = Visit(exp); + + switch (exp.NodeType) + { + case ExpressionType.MemberAccess: + // true property, i.e. x => Trashed + SqlParameters.Add(true); + + //don't execute if compiled + if (Visited == false) + { + return string.Format("({0} = @{1})", o, SqlParameters.Count - 1); + } + //already compiled, return + return string.Empty; + default: + // could be anything else, such as: x => x.Path.StartsWith("-20") + return o; + } + } + + protected virtual string VisitNewArray(NewArrayExpression na) + { + var exprs = VisitExpressionList(na.Expressions); + + //don't execute if compiled + if (Visited == false) + { + var r = new StringBuilder(); + foreach (var e in exprs) + { + r.Append(r.Length > 0 ? "," + e : e); + } + + return r.ToString(); + } + //already compiled, return + return string.Empty; + } + + protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) + { + var exprs = VisitExpressionList(na.Expressions); + return exprs; + } + + protected virtual string BindOperant(ExpressionType e) + { + switch (e) + { + case ExpressionType.Equal: + return "="; + case ExpressionType.NotEqual: + return "<>"; + case ExpressionType.GreaterThan: + return ">"; + case ExpressionType.GreaterThanOrEqual: + return ">="; + case ExpressionType.LessThan: + return "<"; + case ExpressionType.LessThanOrEqual: + return "<="; + case ExpressionType.AndAlso: + return "AND"; + case ExpressionType.OrElse: + return "OR"; + case ExpressionType.Add: + return "+"; + case ExpressionType.Subtract: + return "-"; + case ExpressionType.Multiply: + return "*"; + case ExpressionType.Divide: + return "/"; + case ExpressionType.Modulo: + return "MOD"; + case ExpressionType.Coalesce: + return "COALESCE"; + default: + return e.ToString(); + } + } + + protected virtual string VisitMethodCall(MethodCallExpression m) + { + //Here's what happens with a MethodCallExpression: + // If a method is called that contains a single argument, + // then m.Object is the object on the left hand side of the method call, example: + // x.Path.StartsWith(content.Path) + // m.Object = x.Path + // and m.Arguments.Length == 1, therefor m.Arguments[0] == content.Path + // If a method is called that contains multiple arguments, then m.Object == null and the + // m.Arguments collection contains the left hand side of the method call, example: + // x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar) + // m.Object == null + // m.Arguments.Length == 3, therefor, m.Arguments[0] == x.Path, m.Arguments[1] == content.Path, m.Arguments[2] == TextColumnType.NVarchar + // So, we need to cater for these scenarios. + + var objectForMethod = m.Object ?? m.Arguments[0]; + var visitedObjectForMethod = Visit(objectForMethod); + var methodArgs = m.Object == null + ? m.Arguments.Skip(1).ToArray() + : m.Arguments.ToArray(); + + switch (m.Method.Name) + { + case "ToString": + SqlParameters.Add(objectForMethod.ToString()); + //don't execute if compiled + if (Visited == false) + return string.Format("@{0}", SqlParameters.Count - 1); + //already compiled, return + return string.Empty; + case "ToUpper": + //don't execute if compiled + if (Visited == false) + return string.Format("upper({0})", visitedObjectForMethod); + //already compiled, return + return string.Empty; + case "ToLower": + //don't execute if compiled + if (Visited == false) + return string.Format("lower({0})", visitedObjectForMethod); + //already compiled, return + return string.Empty; + case "SqlWildcard": + case "StartsWith": + case "EndsWith": + case "Contains": + case "Equals": + case "SqlStartsWith": + case "SqlEndsWith": + case "SqlContains": + case "SqlEquals": + case "InvariantStartsWith": + case "InvariantEndsWith": + case "InvariantContains": + case "InvariantEquals": + + string compareValue; + + if (methodArgs[0].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + compareValue = getter().ToString(); + } + else + { + compareValue = methodArgs[0].ToString(); + } + + //special case, if it is 'Contains' and the member that Contains is being called on is not a string, then + // we should be doing an 'In' clause - but we currently do not support this + if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + //default column type + var colType = TextColumnType.NVarchar; + + //then check if the col type argument has been passed to the current method (this will be the case for methods like + // SqlContains and other Sql methods) + if (methodArgs.Length > 1) + { + var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); + if (colTypeArg != null) + { + colType = (TextColumnType)((ConstantExpression)colTypeArg).Value; + } + } + + return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); + + case "Replace": + string searchValue; + + if (methodArgs[0].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + searchValue = getter().ToString(); + } + else + { + searchValue = methodArgs[0].ToString(); + } + + if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + string replaceValue; + + if (methodArgs[1].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[1], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + replaceValue = getter().ToString(); + } + else + { + replaceValue = methodArgs[1].ToString(); + } + + if (methodArgs[1].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[1].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + SqlParameters.Add(RemoveQuote(searchValue)); + + SqlParameters.Add(RemoveQuote(replaceValue)); + + //don't execute if compiled + if (Visited == false) + return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); + //already compiled, return + return string.Empty; + + //case "Substring": + // var startIndex = Int32.Parse(args[0].ToString()) + 1; + // if (args.Count == 2) + // { + // var length = Int32.Parse(args[1].ToString()); + // return string.Format("substring({0} from {1} for {2})", + // r, + // startIndex, + // length); + // } + // else + // return string.Format("substring({0} from {1})", + // r, + // startIndex); + //case "Round": + //case "Floor": + //case "Ceiling": + //case "Coalesce": + //case "Abs": + //case "Sum": + // return string.Format("{0}({1}{2})", + // m.Method.Name, + // r, + // args.Count == 1 ? string.Format(",{0}", args[0]) : ""); + //case "Concat": + // var s = new StringBuilder(); + // foreach (Object e in args) + // { + // s.AppendFormat(" || {0}", e); + // } + // return string.Format("{0}{1}", r, s); + + //case "In": + + // var member = Expression.Convert(m.Arguments[0], typeof(object)); + // var lambda = Expression.Lambda>(member); + // var getter = lambda.Compile(); + + // var inArgs = (object[])getter(); + + // var sIn = new StringBuilder(); + // foreach (var e in inArgs) + // { + // SqlParameters.Add(e); + + // sIn.AppendFormat("{0}{1}", + // sIn.Length > 0 ? "," : "", + // string.Format("@{0}", SqlParameters.Count - 1)); + + // //sIn.AppendFormat("{0}{1}", + // // sIn.Length > 0 ? "," : "", + // // GetQuotedValue(e, e.GetType())); + // } + + // return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString()); + //case "Desc": + // return string.Format("{0} DESC", r); + //case "Alias": + //case "As": + // return string.Format("{0} As {1}", r, + // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); + + default: + + throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); + + //var s2 = new StringBuilder(); + //foreach (Object e in args) + //{ + // s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); + //} + //return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); + } + } + + public virtual string GetQuotedTableName(string tableName) + { + return Visited ? tableName : string.Format("\"{0}\"", tableName); + } + + public virtual string GetQuotedColumnName(string columnName) + { + return Visited ? columnName : string.Format("\"{0}\"", columnName); + } + + public virtual string GetQuotedName(string name) + { + return Visited ? name : string.Format("\"{0}\"", name); + } + + protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType) + { + switch (verb) + { + case "SqlWildcard": + SqlParameters.Add(RemoveQuote(val)); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "Equals": + SqlParameters.Add(RemoveQuote(val)); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "StartsWith": + SqlParameters.Add(string.Format("{0}{1}", + RemoveQuote(val), + SqlSyntax.GetWildcardPlaceholder())); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "EndsWith": + SqlParameters.Add(string.Format("{0}{1}", + SqlSyntax.GetWildcardPlaceholder(), + RemoveQuote(val))); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "Contains": + SqlParameters.Add(string.Format("{0}{1}{0}", + SqlSyntax.GetWildcardPlaceholder(), + RemoveQuote(val))); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "InvariantEquals": + case "SqlEquals": + //recurse + return HandleStringComparison(col, val, "Equals", columnType); + case "InvariantStartsWith": + case "SqlStartsWith": + //recurse + return HandleStringComparison(col, val, "StartsWith", columnType); + case "InvariantEndsWith": + case "SqlEndsWith": + //recurse + return HandleStringComparison(col, val, "EndsWith", columnType); + case "InvariantContains": + case "SqlContains": + //recurse + return HandleStringComparison(col, val, "Contains", columnType); + default: + throw new ArgumentOutOfRangeException("verb"); + } + } + + //public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null) + //{ + // if (value == null) return "NULL"; + + // if (escapeCallback == null) + // { + // escapeCallback = EscapeParam; + // } + // if (shouldQuoteCallback == null) + // { + // shouldQuoteCallback = ShouldQuoteValue; + // } + + // if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string)) + // { + // //if (TypeSerializer.CanCreateFromString(fieldType)) + // //{ + // // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'"; + // //} + + // throw new NotSupportedException( + // string.Format("Property of type: {0} is not supported", fieldType.FullName)); + // } + + // if (fieldType == typeof(int)) + // return ((int)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(float)) + // return ((float)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(double)) + // return ((double)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(decimal)) + // return ((decimal)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(DateTime)) + // { + // return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'"; + // } + + // if (fieldType == typeof(bool)) + // return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture); + + // return shouldQuoteCallback(fieldType) + // ? "'" + escapeCallback(value) + "'" + // : value.ToString(); + //} + + public virtual string EscapeParam(object paramValue) + { + return paramValue == null ? string.Empty : SqlSyntax.EscapeString(paramValue.ToString()); + } + + public virtual bool ShouldQuoteValue(Type fieldType) + { + return true; + } + + protected virtual string RemoveQuote(string exp) + { + if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) + && + (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) + { + exp = exp.Remove(0, 1); + exp = exp.Remove(exp.Length - 1, 1); + } + return exp; + } + + //protected virtual string RemoveQuoteFromAlias(string expression) + //{ + + // if ((expression.StartsWith("\"") || expression.StartsWith("`") || expression.StartsWith("'")) + // && + // (expression.EndsWith("\"") || expression.EndsWith("`") || expression.EndsWith("'"))) + // { + // expression = expression.Remove(0, 1); + // expression = expression.Remove(expression.Length - 1, 1); + // } + // return expression; + //} + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs similarity index 65% rename from src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs rename to src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs index 7e4466b529..b265a5b587 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs @@ -1,81 +1,74 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Querying -{ - /// - /// An expression tree parser to create SQL statements and SQL parameters based on a given strongly typed expression based on Umbraco's business logic Models - /// - /// - /// This object stores state, it cannot be re-used to parse an expression - /// - internal class ModelToSqlExpressionHelper : BaseExpressionHelper - { - - private readonly BaseMapper _mapper; - - public ModelToSqlExpressionHelper(ISqlSyntaxProvider sqlSyntax, BaseMapper mapper) : base(sqlSyntax) - { - _mapper = mapper; - } - - public ModelToSqlExpressionHelper() : this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T))) - { - } - - protected override string VisitMemberAccess(MemberExpression m) - { - if (m.Expression != null && - m.Expression.NodeType == ExpressionType.Parameter - && m.Expression.Type == typeof(T)) - { - //don't execute if compiled - if (IsCompiled == false) - { - var field = _mapper.Map(m.Member.Name, true); - if (field.IsNullOrWhiteSpace()) - throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name); - return field; - } - //already compiled, return - return string.Empty; - } - - if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert) - { - //don't execute if compiled - if (IsCompiled == false) - { - var field = _mapper.Map(m.Member.Name, true); - if (field.IsNullOrWhiteSpace()) - throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name); - return field; - } - //already compiled, return - return string.Empty; - } - - var member = Expression.Convert(m, typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - object o = getter(); - - SqlParameters.Add(o); - - //don't execute if compiled - if (IsCompiled == false) - return string.Format("@{0}", SqlParameters.Count - 1); - //already compiled, return - return string.Empty; - - } - } +using System; +using System.Linq.Expressions; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression, + /// based on Umbraco's business logic models. + /// + /// This object is stateful and cannot be re-used to parse an expression. + internal class ModelToSqlExpressionVisitor : ExpressionVisitorBase + { + private readonly BaseMapper _mapper; + + public ModelToSqlExpressionVisitor(ISqlSyntaxProvider sqlSyntax, BaseMapper mapper) + : base(sqlSyntax) + { + _mapper = mapper; + } + + public ModelToSqlExpressionVisitor() + : this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T))) + { } + + protected override string VisitMemberAccess(MemberExpression m) + { + if (m.Expression != null && + m.Expression.NodeType == ExpressionType.Parameter + && m.Expression.Type == typeof(T)) + { + //don't execute if compiled + if (Visited == false) + { + var field = _mapper.Map(m.Member.Name, true); + if (field.IsNullOrWhiteSpace()) + throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name); + return field; + } + //already compiled, return + return string.Empty; + } + + if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert) + { + //don't execute if compiled + if (Visited == false) + { + var field = _mapper.Map(m.Member.Name, true); + if (field.IsNullOrWhiteSpace()) + throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name); + return field; + } + //already compiled, return + return string.Empty; + } + + var member = Expression.Convert(m, typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + var o = getter(); + + SqlParameters.Add(o); + + //don't execute if compiled + if (Visited == false) + return string.Format("@{0}", SqlParameters.Count - 1); + //already compiled, return + return string.Empty; + + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs similarity index 73% rename from src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs rename to src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs index cbc3aa8f05..6b527296df 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs @@ -1,79 +1,73 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Querying -{ - - /// - /// An expression tree parser to create SQL statements and SQL parameters based on a given strongly typed expression based on Umbraco's PetaPoco dto Models - /// - /// - /// This object stores state, it cannot be re-used to parse an expression - /// - internal class PocoToSqlExpressionHelper : BaseExpressionHelper - { - private readonly Database.PocoData _pd; - - public PocoToSqlExpressionHelper() : base(SqlSyntaxContext.SqlSyntaxProvider) - { - _pd = new Database.PocoData(typeof(T)); - } - - protected override string VisitMemberAccess(MemberExpression m) - { - if (m.Expression != null && - m.Expression.NodeType == ExpressionType.Parameter - && m.Expression.Type == typeof(T)) - { - //don't execute if compiled - if (IsCompiled == false) - { - string field = GetFieldName(_pd, m.Member.Name); - return field; - } - //already compiled, return - return string.Empty; - } - - if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert) - { - //don't execute if compiled - if (IsCompiled == false) - { - string field = GetFieldName(_pd, m.Member.Name); - return field; - } - //already compiled, return - return string.Empty; - } - - var member = Expression.Convert(m, typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - object o = getter(); - - SqlParameters.Add(o); - - //don't execute if compiled - if (IsCompiled == false) - return string.Format("@{0}", SqlParameters.Count - 1); - //already compiled, return - return string.Empty; - } - - protected virtual string GetFieldName(Database.PocoData pocoData, string name) - { - var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name); - return string.Format("{0}.{1}", - SqlSyntax.GetQuotedTableName(pocoData.TableInfo.TableName), - SqlSyntax.GetQuotedColumnName(column.Value.ColumnName)); - } - - } +using System; +using System.Linq; +using System.Linq.Expressions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression, + /// based on Umbraco's DTOs. + /// + /// This object is stateful and cannot be re-used to parse an expression. + internal class PocoToSqlExpressionVisitor : ExpressionVisitorBase + { + private readonly Database.PocoData _pd; + + public PocoToSqlExpressionVisitor() + : base(SqlSyntaxContext.SqlSyntaxProvider) + { + _pd = new Database.PocoData(typeof(T)); + } + + protected override string VisitMemberAccess(MemberExpression m) + { + if (m.Expression != null && + m.Expression.NodeType == ExpressionType.Parameter + && m.Expression.Type == typeof(T)) + { + //don't execute if compiled + if (Visited == false) + { + string field = GetFieldName(_pd, m.Member.Name); + return field; + } + //already compiled, return + return string.Empty; + } + + if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert) + { + //don't execute if compiled + if (Visited == false) + { + string field = GetFieldName(_pd, m.Member.Name); + return field; + } + //already compiled, return + return string.Empty; + } + + var member = Expression.Convert(m, typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + var o = getter(); + + SqlParameters.Add(o); + + //don't execute if compiled + if (Visited == false) + return string.Format("@{0}", SqlParameters.Count - 1); + //already compiled, return + return string.Empty; + } + + protected virtual string GetFieldName(Database.PocoData pocoData, string name) + { + var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name); + return string.Format("{0}.{1}", + SqlSyntax.GetQuotedTableName(pocoData.TableInfo.TableName), + SqlSyntax.GetQuotedColumnName(column.Value.ColumnName)); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/Query.cs b/src/Umbraco.Core/Persistence/Querying/Query.cs index 1a270cec4b..6213ca5ed6 100644 --- a/src/Umbraco.Core/Persistence/Querying/Query.cs +++ b/src/Umbraco.Core/Persistence/Querying/Query.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Querying if (predicate != null) { //TODO: This should have an SqlSyntax object passed in, this ctor is relying on a singleton - var expressionHelper = new ModelToSqlExpressionHelper(); + var expressionHelper = new ModelToSqlExpressionVisitor(); string whereExpression = expressionHelper.Visit(predicate); _wheres.Add(new Tuple(whereExpression, expressionHelper.GetSqlParameters())); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 36894801aa..bc1aa0c8f6 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1016,10 +1016,10 @@ - - + + - + diff --git a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs index 2b1561012b..03aa3206ee 100644 --- a/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/ModelToSqlExpressionHelperBenchmarks.cs @@ -62,7 +62,7 @@ namespace Umbraco.Tests.Benchmarks Expression> predicate = content => content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b); - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(_syntaxProvider, _contentMapper); + var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(_syntaxProvider, _contentMapper); var result = modelToSqlExpressionHelper.Visit(predicate); } @@ -80,7 +80,7 @@ namespace Umbraco.Tests.Benchmarks Expression> predicate = content => content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b); - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(_syntaxProvider, _contentMapper); + var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(_syntaxProvider, _contentMapper); //wrap it! _cachedExpression.Wrap(predicate); diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index 6834968460..65a660146e 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Persistence.Querying // { // //Arrange // Expression> predicate = content => content.ContentType.Alias == "Test"; - // var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + // var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(); // var result = modelToSqlExpressionHelper.Visit(predicate); // Debug.Print("Model to Sql ExpressionHelper: \n" + result); @@ -42,14 +42,14 @@ namespace Umbraco.Tests.Persistence.Querying Expression> predicate1 = content => content.Path.StartsWith("-1"); cachedExpression.Wrap(predicate1); - var modelToSqlExpressionHelper1 = new ModelToSqlExpressionHelper(); + var modelToSqlExpressionHelper1 = new ModelToSqlExpressionVisitor(); var result1 = modelToSqlExpressionHelper1.Visit(cachedExpression); Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result1); Assert.AreEqual("-1%", modelToSqlExpressionHelper1.GetSqlParameters()[0]); Expression> predicate2 = content => content.Path.StartsWith("-1,123,97"); cachedExpression.Wrap(predicate2); - var modelToSqlExpressionHelper2 = new ModelToSqlExpressionHelper(); + var modelToSqlExpressionHelper2 = new ModelToSqlExpressionVisitor(); var result2 = modelToSqlExpressionHelper2.Visit(cachedExpression); Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result2); Assert.AreEqual("-1,123,97%", modelToSqlExpressionHelper2.GetSqlParameters()[0]); @@ -61,7 +61,7 @@ namespace Umbraco.Tests.Persistence.Querying { //Arrange Expression> predicate = content => content.Path.StartsWith("-1"); - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(); var result = modelToSqlExpressionHelper.Visit(predicate); Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result); @@ -73,7 +73,7 @@ namespace Umbraco.Tests.Persistence.Querying { //Arrange Expression> predicate = content => content.ParentId == -1; - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(); var result = modelToSqlExpressionHelper.Visit(predicate); Debug.Print("Model to Sql ExpressionHelper: \n" + result); @@ -86,7 +86,7 @@ namespace Umbraco.Tests.Persistence.Querying public void Equals_Operator_For_Value_Gets_Escaped() { Expression> predicate = user => user.Username == "hello@world.com"; - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(); var result = modelToSqlExpressionHelper.Visit(predicate); Debug.Print("Model to Sql ExpressionHelper: \n" + result); @@ -99,7 +99,7 @@ namespace Umbraco.Tests.Persistence.Querying public void Equals_Method_For_Value_Gets_Escaped() { Expression> predicate = user => user.Username.Equals("hello@world.com"); - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(); var result = modelToSqlExpressionHelper.Visit(predicate); Debug.Print("Model to Sql ExpressionHelper: \n" + result); @@ -115,7 +115,7 @@ namespace Umbraco.Tests.Persistence.Querying SqlSyntaxContext.SqlSyntaxProvider = new MySqlSyntaxProvider(Mock.Of()); Expression> predicate = user => user.Username.Equals("mydomain\\myuser"); - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(); var result = modelToSqlExpressionHelper.Visit(predicate); Debug.Print("Model to Sql ExpressionHelper: \n" + result); @@ -132,7 +132,7 @@ namespace Umbraco.Tests.Persistence.Querying SqlSyntaxContext.SqlSyntaxProvider = new MySqlSyntaxProvider(Mock.Of()); Expression> predicate = user => user.Login.StartsWith("mydomain\\myuser"); - var modelToSqlExpressionHelper = new PocoToSqlExpressionHelper(); + var modelToSqlExpressionHelper = new PocoToSqlExpressionVisitor(); var result = modelToSqlExpressionHelper.Visit(predicate); Debug.Print("Poco to Sql ExpressionHelper: \n" + result); @@ -145,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Querying public void Sql_Replace_Mapped() { Expression> predicate = user => user.Username.Replace("@world", "@test") == "hello@test.com"; - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(); + var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(); var result = modelToSqlExpressionHelper.Visit(predicate); Debug.Print("Model to Sql ExpressionHelper: \n" + result); From 69b442a31a931a7d7390698c368ca1f377505fb1 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 31 Oct 2016 11:02:00 +0100 Subject: [PATCH 14/88] Fix - PublicAccessEntry rules clearing --- src/Umbraco.Core/Models/PublicAccessEntry.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 4af6f9536a..1ed15a8fb4 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Models NoAccessNodeId = noAccessNode.Id; _protectedNodeId = protectedNode.Id; - _ruleCollection = new ObservableCollection(ruleCollection); + _ruleCollection = new ObservableCollection(ruleCollection); _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; } @@ -81,7 +81,7 @@ namespace Umbraco.Core.Models internal IEnumerable RemovedRules { get { return _removedRules; } - } + } public IEnumerable Rules { @@ -107,10 +107,7 @@ namespace Umbraco.Core.Models public void ClearRules() { - for (var i = _ruleCollection.Count - 1; i >= 0; i--) - { - RemoveRule(_ruleCollection[i]); - } + _ruleCollection.Clear(); } [DataMember] @@ -126,7 +123,7 @@ namespace Umbraco.Core.Models get { return _noAccessNodeId; } set { SetPropertyValueAndDetectChanges(value, ref _noAccessNodeId, Ps.Value.NoAccessNodeIdSelector); } } - + [DataMember] public int ProtectedNodeId { From c77174059d41ffc69cbbbe940b2d508bf4b8096a Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 31 Oct 2016 11:06:17 +0100 Subject: [PATCH 15/88] Backport MainDom cleanup from 7.6 --- src/Umbraco.Core/MainDom.cs | 68 +++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index fb8ad06999..8e7efc5171 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -1,18 +1,21 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.IO.MemoryMappedFiles; -using System.Text; using System.Threading; -using System.Threading.Tasks; using System.Web.Hosting; using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; namespace Umbraco.Core { - // represents the main domain - class MainDom : IRegisteredObject + /// + /// Represents the main AppDomain running for a given application. + /// + /// + /// There can be only one "main" AppDomain running for a given application at a time. + /// When an AppDomain starts, it tries to acquire the main domain status. + /// When an AppDomain stops (eg the application is restarting) it should release the main domain status. + /// It is possible to register against the MainDom and be notified when it is released. + /// + internal class MainDom : IRegisteredObject { #region Vars @@ -34,16 +37,26 @@ namespace Umbraco.Core private volatile bool _signaled; // we have been signaled // actions to run before releasing the main domain - private readonly SortedList _callbacks = new SortedList(); + private readonly SortedList _callbacks = new SortedList(new WeightComparer()); private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds + private class WeightComparer : IComparer + { + public int Compare(int x, int y) + { + var result = x.CompareTo(y); + // return "equal" as "greater than" + return result == 0 ? 1 : result; + } + } + #endregion #region Ctor // initializes a new instance of MainDom - public MainDom(ILogger logger) + internal MainDom(ILogger logger) { _logger = logger; @@ -52,22 +65,47 @@ namespace Umbraco.Core if (HostingEnvironment.ApplicationID != null) appId = HostingEnvironment.ApplicationID.ReplaceNonAlphanumericChars(string.Empty); - var lockName = "UMBRACO-" + appId + "-MAINDOM-LCK"; + // combining with the physical path because if running on eg IIS Express, + // two sites could have the same appId even though they are different. + // + // now what could still collide is... two sites, running in two different processes + // and having the same appId, and running on the same app physical path + // + // we *cannot* use the process ID here because when an AppPool restarts it is + // a new process for the same application path + + var appPath = HostingEnvironment.ApplicationPhysicalPath; + var hash = (appId + ":::" + appPath).ToSHA1(); + + var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK"; _asyncLock = new AsyncLock(lockName); - var eventName = "UMBRACO-" + appId + "-MAINDOM-EVT"; + var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT"; _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); } #endregion - // register a main domain consumer + /// + /// Registers a resource that requires the current AppDomain to be the main domain to function. + /// + /// An action to execute before the AppDomain releases the main domain status. + /// An optional weight (lower goes first). + /// A value indicating whether it was possible to register. public bool Register(Action release, int weight = 100) { return Register(null, release, weight); } - // register a main domain consumer + /// + /// Registers a resource that requires the current AppDomain to be the main domain to function. + /// + /// An action to execute when registering. + /// An action to execute before the AppDomain releases the main domain status. + /// An optional weight (lower goes first). + /// A value indicating whether it was possible to register. + /// If registering is successful, then the action + /// is guaranteed to execute before the AppDomain releases the main domain status. public bool Register(Action install, Action release, int weight = 100) { lock (_locko) @@ -123,7 +161,7 @@ namespace Umbraco.Core } // acquires the main domain - public bool Acquire() + internal bool Acquire() { lock (_locko) // we don't want the hosting environment to interfere by signaling { @@ -174,7 +212,7 @@ namespace Umbraco.Core } // IRegisteredObject - public void Stop(bool immediate) + void IRegisteredObject.Stop(bool immediate) { try { From 4d13622432e0fc83423ab42d174d3228cca043da Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 31 Oct 2016 14:55:11 +0100 Subject: [PATCH 16/88] U4-9132 Macros from packages are empty when restored changing the package installer to use the new API macroservice instead of the old legacy classes. marked a few methods as obsoleted. --- .../businesslogic/Packager/Installer.cs | 15 ++----- src/umbraco.cms/businesslogic/macro/Macro.cs | 39 ++++++++++--------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index bc1c062631..dbbdf08e21 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -411,19 +411,12 @@ namespace umbraco.cms.businesslogic.packager #endregion #region Macros - foreach (XmlNode n in Config.DocumentElement.SelectNodes("//macro")) + var macroItemsElement = rootElement.Descendants("Macros").FirstOrDefault(); + if (macroItemsElement != null) { - //TODO: Fix this, this should not use the legacy API - Macro m = Macro.Import(n); - - if (m != null) - { - insPack.Data.Macros.Add(m.Id.ToString(CultureInfo.InvariantCulture)); - //saveNeeded = true; - } + var insertedMacros = packagingService.ImportMacros(macroItemsElement); + insPack.Data.Macros.AddRange(insertedMacros.Select(m => m.Id.ToString())); } - - //if (saveNeeded) { insPack.Save(); saveNeeded = false; } #endregion #region Templates diff --git a/src/umbraco.cms/businesslogic/macro/Macro.cs b/src/umbraco.cms/businesslogic/macro/Macro.cs index 4ff30c404f..4031f54150 100644 --- a/src/umbraco.cms/businesslogic/macro/Macro.cs +++ b/src/umbraco.cms/businesslogic/macro/Macro.cs @@ -1,11 +1,8 @@ using System; -using System.Data; -using System.Globalization; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Xml; -using System.Runtime.CompilerServices; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.IO; @@ -185,23 +182,26 @@ namespace umbraco.cms.businesslogic.macro }).ToArray(); } } - - /// - /// Macro initializer - /// - public Macro() + + /// + /// Macro initializer + /// + [Obsolete("This should no longer be used, use the IMacroService and related models instead")] + public Macro() { } - /// - /// Macro initializer - /// - /// The id of the macro - public Macro(int Id) + /// + /// Macro initializer + /// + /// The id of the macro + [Obsolete("This should no longer be used, use the IMacroService and related models instead")] + public Macro(int Id) { Setup(Id); } + [Obsolete("This should no longer be used, use the IMacroService and related models instead")] internal Macro(IMacro macro) { MacroEntity = macro; @@ -211,15 +211,17 @@ namespace umbraco.cms.businesslogic.macro /// Initializes a new instance of the class. /// /// The alias. + [Obsolete("This should no longer be used, use the IMacroService and related models instead")] public Macro(string alias) { Setup(alias); } - /// - /// Used to persist object changes to the database. In Version3.0 it's just a stub for future compatibility - /// - public virtual void Save() + /// + /// Used to persist object changes to the database. In Version3.0 it's just a stub for future compatibility + /// + [Obsolete("This should no longer be used, use the IMacroService and related models instead")] + public virtual void Save() { //event var e = new SaveEventArgs(); @@ -250,8 +252,7 @@ namespace umbraco.cms.businesslogic.macro } } - //TODO: Fix this, this should wrap a new API! - + [Obsolete("This is no longer used, use the IMacroService and related models instead")] public static Macro Import(XmlNode n) { var alias = XmlHelper.GetNodeValue(n.SelectSingleNode("alias")); From e8237f61900cbff261cdc1824b28d54ff9f796ce Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 31 Oct 2016 15:09:10 +0100 Subject: [PATCH 17/88] adding culture parameters for this and a few others that were missing. --- src/umbraco.cms/businesslogic/Packager/Installer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index dbbdf08e21..1f17da3922 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -396,7 +396,7 @@ namespace umbraco.cms.businesslogic.packager if (languageItemsElement != null) { var insertedLanguages = packagingService.ImportLanguages(languageItemsElement); - insPack.Data.Languages.AddRange(insertedLanguages.Select(l => l.Id.ToString())); + insPack.Data.Languages.AddRange(insertedLanguages.Select(l => l.Id.ToString(CultureInfo.InvariantCulture))); } #endregion @@ -406,7 +406,7 @@ namespace umbraco.cms.businesslogic.packager if (dictionaryItemsElement != null) { var insertedDictionaryItems = packagingService.ImportDictionaryItems(dictionaryItemsElement); - insPack.Data.DictionaryItems.AddRange(insertedDictionaryItems.Select(d => d.Id.ToString())); + insPack.Data.DictionaryItems.AddRange(insertedDictionaryItems.Select(d => d.Id.ToString(CultureInfo.InvariantCulture))); } #endregion @@ -415,7 +415,7 @@ namespace umbraco.cms.businesslogic.packager if (macroItemsElement != null) { var insertedMacros = packagingService.ImportMacros(macroItemsElement); - insPack.Data.Macros.AddRange(insertedMacros.Select(m => m.Id.ToString())); + insPack.Data.Macros.AddRange(insertedMacros.Select(m => m.Id.ToString(CultureInfo.InvariantCulture))); } #endregion @@ -454,7 +454,7 @@ namespace umbraco.cms.businesslogic.packager { StyleSheet s = StyleSheet.Import(n, currentUser); - insPack.Data.Stylesheets.Add(s.Id.ToString()); + insPack.Data.Stylesheets.Add(s.Id.ToString(CultureInfo.InvariantCulture)); //saveNeeded = true; } From 3a6e2e673518ccccecf1fa03bbfe81a7e6a2e9d8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 2 Nov 2016 12:39:41 +0100 Subject: [PATCH 18/88] U4-9136 - rebuild Xml data in a more resilient way --- .../Repositories/ContentRepository.cs | 101 ++++++------------ .../Repositories/MediaRepository.cs | 93 ++++++---------- .../Repositories/MemberRepository.cs | 93 ++++++---------- .../Repositories/ContentRepositoryTest.cs | 36 +++---- .../Repositories/MediaRepositoryTest.cs | 38 ++++++- .../Repositories/MemberRepositoryTest.cs | 8 +- 6 files changed, 155 insertions(+), 214 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 84caeb2a3e..70b968032b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -175,85 +175,50 @@ namespace Umbraco.Core.Persistence.Repositories public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) { + // the previous way of doing this was to run it all in one big transaction, + // and to bulk-insert groups of xml rows - which works, until the transaction + // times out - and besides, because v7 transactions are ReadCommited, it does + // not bring much safety - so this reverts to updating each record individually, + // and it may be slower in the end, but should be more resilient. - //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. - using (var tr = Database.GetTransaction()) + var baseId = 0; + var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray(); + while (true) { - //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted - if (contentTypeIds == null) - { - var subQuery = new Sql() - .Select("id") - .From(SqlSyntax) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + // get the next group of nodes + var query = GetBaseQuery(false); + if (contentTypeIdsA.Length > 0) + query = query + .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); + query = query + .Where(x => x.NodeId > baseId && x.Trashed == false) + .Where(x => x.Published) + .OrderBy(x => x.NodeId, SqlSyntax); + var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) + .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) + .ToList(); - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - else - { - var subQuery = new Sql() - .Select("umbracoNode.id as nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .WhereIn(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + // no more nodes, break + if (xmlItems.Count == 0) break; - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - - //now insert the data, again if something fails here, the whole transaction is reversed - if (contentTypeIds == null) + foreach (var xmlItem in xmlItems) { - var query = Query.Builder.Where(x => x.Published == true); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) + try { - //copy local - var id = contentTypeId; - var query = Query.Builder.Where(x => x.Published == true && x.ContentTypeId == id && x.Trashed == false); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + // InsertOrUpdate tries to update first, which is good since it is what + // should happen in most cases, then it tries to insert, and it should work + // unless the node has been deleted, and we just report the exception + Database.InsertOrUpdate(xmlItem); + } + catch (Exception e) + { + Logger.Error("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e); } } - - tr.Complete(); + baseId = xmlItems.Last().NodeId; } } - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction tr, int pageSize) - { - var pageIndex = 0; - var total = long.MinValue; - var processed = 0; - do - { - //NOTE: This is an important call, we cannot simply make a call to: - // GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending); - // because that method is used to query 'latest' content items where in this case we don't necessarily - // want latest content items because a pulished content item might not actually be the latest. - // see: http://issues.umbraco.org/issue/U4-6322 & http://issues.umbraco.org/issue/U4-5982 - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, - new Tuple("cmsDocument", "nodeId"), - ProcessQuery, "Path", Direction.Ascending, true); - - var xmlItems = (from descendant in descendants - let xml = serializer(descendant) - select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }); - - //bulk insert it into the database - var count = Database.BulkInsertRecords(xmlItems, tr, SqlSyntax); - - processed += count; - - pageIndex++; - } while (processed < total); - } - public override IContent GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 598c9e912d..d3356cd4f6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -165,78 +165,49 @@ namespace Umbraco.Core.Persistence.Repositories public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) { + // the previous way of doing this was to run it all in one big transaction, + // and to bulk-insert groups of xml rows - which works, until the transaction + // times out - and besides, because v7 transactions are ReadCommited, it does + // not bring much safety - so this reverts to updating each record individually, + // and it may be slower in the end, but should be more resilient. - //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. - using (var tr = Database.GetTransaction()) + var baseId = 0; + var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray(); + while (true) { - //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted - if (contentTypeIds == null) - { - var subQuery = new Sql() - .Select("id") - .From(SqlSyntax) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + // get the next group of nodes + var query = GetBaseQuery(false); + if (contentTypeIdsA.Length > 0) + query = query + .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); + query = query + .Where(x => x.NodeId > baseId) + .OrderBy(x => x.NodeId, SqlSyntax); + var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) + .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) + .ToList(); - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - else - { - var subQuery = new Sql() - .Select("umbracoNode.id as nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .WhereIn(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + // no more nodes, break + if (xmlItems.Count == 0) break; - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - - //now insert the data, again if something fails here, the whole transaction is reversed - if (contentTypeIds == null) + foreach (var xmlItem in xmlItems) { - var query = Query.Builder; - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) + try { - //copy local - var id = contentTypeId; - var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + // InsertOrUpdate tries to update first, which is good since it is what + // should happen in most cases, then it tries to insert, and it should work + // unless the node has been deleted, and we just report the exception + Database.InsertOrUpdate(xmlItem); + } + catch (Exception e) + { + Logger.Error("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e); } } - - tr.Complete(); + baseId = xmlItems.Last().NodeId; } } - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction tr, int pageSize) - { - var pageIndex = 0; - var total = long.MinValue; - var processed = 0; - do - { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); - - var xmlItems = (from descendant in descendants - let xml = serializer(descendant) - select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); - - //bulk insert it into the database - Database.BulkInsertRecords(xmlItems, tr); - - processed += xmlItems.Length; - - pageIndex++; - } while (processed < total); - } - public void AddOrUpdateContentXml(IMedia content, Func xml) { _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 216fc223db..05f030f51d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -382,78 +382,49 @@ namespace Umbraco.Core.Persistence.Repositories public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) { + // the previous way of doing this was to run it all in one big transaction, + // and to bulk-insert groups of xml rows - which works, until the transaction + // times out - and besides, because v7 transactions are ReadCommited, it does + // not bring much safety - so this reverts to updating each record individually, + // and it may be slower in the end, but should be more resilient. - //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. - using (var tr = Database.GetTransaction()) + var baseId = 0; + var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray(); + while (true) { - //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted - if (contentTypeIds == null) - { - var subQuery = new Sql() - .Select("id") - .From(SqlSyntax) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + // get the next group of nodes + var query = GetBaseQuery(false); + if (contentTypeIdsA.Length > 0) + query = query + .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); + query = query + .Where(x => x.NodeId > baseId) + .OrderBy(x => x.NodeId, SqlSyntax); + var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) + .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) + .ToList(); - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - else - { - var subQuery = new Sql() - .Select("umbracoNode.id as nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .WhereIn(dto => dto.ContentTypeId, contentTypeIds, SqlSyntax) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + // no more nodes, break + if (xmlItems.Count == 0) break; - var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); - } - - //now insert the data, again if something fails here, the whole transaction is reversed - if (contentTypeIds == null) + foreach (var xmlItem in xmlItems) { - var query = Query.Builder; - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) + try { - //copy local - var id = contentTypeId; - var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + // InsertOrUpdate tries to update first, which is good since it is what + // should happen in most cases, then it tries to insert, and it should work + // unless the node has been deleted, and we just report the exception + Database.InsertOrUpdate(xmlItem); + } + catch (Exception e) + { + Logger.Error("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e); } } - - tr.Complete(); + baseId = xmlItems.Last().NodeId; } } - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction tr, int pageSize) - { - var pageIndex = 0; - var total = long.MinValue; - var processed = 0; - do - { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); - - var xmlItems = (from descendant in descendants - let xml = serializer(descendant) - select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); - - //bulk insert it into the database - Database.BulkInsertRecords(xmlItems, tr); - - processed += xmlItems.Length; - - pageIndex++; - } while (processed < total); - } - public override IMember GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index abcb5b3d6a..311ea52071 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -101,7 +101,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (int i = 0; i < allCreated.Count; i++) { allCreated[i].Name = "blah" + i; - //IMPORTANT testing note here: We need to changed the published state here so that + //IMPORTANT testing note here: We need to changed the published state here so that // it doesn't automatically think this is simply publishing again - this forces the latest // version to be Saved and not published allCreated[i].ChangePublishedState(PublishedState.Saved); @@ -109,7 +109,7 @@ namespace Umbraco.Tests.Persistence.Repositories } unitOfWork.Commit(); - //delete all xml + //delete all xml unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); @@ -156,7 +156,7 @@ namespace Umbraco.Tests.Persistence.Repositories } unitOfWork.Commit(); - //delete all xml + //delete all xml unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); @@ -221,7 +221,7 @@ namespace Umbraco.Tests.Persistence.Repositories } unitOfWork.Commit(); - //delete all xml + //delete all xml unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); @@ -234,10 +234,10 @@ namespace Umbraco.Tests.Persistence.Repositories /// /// This test ensures that when property values using special database fields are saved, the actual data in the /// object being stored is also transformed in the same way as the data being stored in the database is. - /// Before you would see that ex: a decimal value being saved as 100 or "100", would be that exact value in the + /// Before you would see that ex: a decimal value being saved as 100 or "100", would be that exact value in the /// object, but the value saved to the database was actually 100.000000. - /// When querying the database for the value again - the value would then differ from what is in the object. - /// This caused inconsistencies between saving+publishing and simply saving and then publishing, due to the former + /// When querying the database for the value again - the value would then differ from what is in the object. + /// This caused inconsistencies between saving+publishing and simply saving and then publishing, due to the former /// sending the non-transformed data directly on to publishing. /// [Test] @@ -269,10 +269,10 @@ namespace Umbraco.Tests.Persistence.Repositories var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage", propertyTypeCollection); contentTypeRepository.AddOrUpdate(contentType); unitOfWork.Commit(); - + // Int and decimal values are passed in as strings as they would be from the backoffice UI var textpage = MockedContent.CreateSimpleContentWithSpecialDatabaseTypes(contentType, "test@umbraco.org", -1, "100", "150", dateValue); - + // Act repository.AddOrUpdate(textpage); unitOfWork.Commit(); @@ -280,7 +280,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(textpage.HasIdentity, Is.True); - + var persistedTextpage = repository.Get(textpage.Id); Assert.That(persistedTextpage.Name, Is.EqualTo(textpage.Name)); Assert.AreEqual(100m, persistedTextpage.GetValue(decimalPropertyAlias)); @@ -342,12 +342,12 @@ namespace Umbraco.Tests.Persistence.Repositories contentTypeRepository.AddOrUpdate(contentType); repository.AddOrUpdate(textpage); unitOfWork.Commit(); - + // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(textpage.HasIdentity, Is.True); - + } } @@ -631,9 +631,9 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Name.Contains("Text")); long totalRecords; - + try - { + { DatabaseContext.Database.EnableSqlTrace = true; DatabaseContext.Database.EnableSqlCount(); @@ -643,14 +643,14 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(2, result.Count()); result = repository.GetPagedResultsByQuery(query, 1, 2, out totalRecords, "title", Direction.Ascending, false); - + Assert.AreEqual(1, result.Count()); } finally - { + { DatabaseContext.Database.EnableSqlTrace = false; DatabaseContext.Database.DisableSqlCount(); - } + } } } @@ -761,7 +761,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); var filterQuery = Query.Builder.Where(x => x.Name.Contains("Page 2")); - + long totalRecords; var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, filterQuery); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index a216eaaa45..1566441d5a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Moq; @@ -59,7 +60,7 @@ namespace Umbraco.Tests.Persistence.Repositories } unitOfWork.Commit(); - //delete all xml + //delete all xml unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); @@ -69,6 +70,39 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Rebuild_Some_Xml_Structures() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var mediaType = mediaTypeRepository.Get(1032); + + IMedia img50 = null; + for (var i = 0; i < 100; i++) + { + var image = MockedMedia.CreateMediaImage(mediaType, -1); + repository.AddOrUpdate(image); + if (i == 50) img50 = image; + } + unitOfWork.Commit(); + + // assume this works (see other test) + repository.RebuildXmlStructures(media => new XElement("test"), 10); + + //delete some xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml WHERE nodeId < " + img50.Id); + Assert.AreEqual(50, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10); + + Assert.AreEqual(103, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + [Test] public void Rebuild_All_Xml_Structures_For_Content_Type() { @@ -99,7 +133,7 @@ namespace Umbraco.Tests.Persistence.Repositories } unitOfWork.Commit(); - //delete all xml + //delete all xml unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index bb13f055c9..e97fff03c8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -54,7 +54,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork, out memberTypeRepository, out memberGroupRepository)) { var memberType1 = CreateTestMemberType(); - + for (var i = 0; i < 100; i++) { var member = MockedMember.CreateSimpleMember(memberType1, "blah" + i, "blah" + i + "@example.com", "blah", "blah" + i); @@ -103,7 +103,7 @@ namespace Umbraco.Tests.Persistence.Repositories } unitOfWork.Commit(); - //delete all xml + //delete all xml unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); @@ -216,7 +216,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sut = repository.Get(member.Id); Assert.That(sut, Is.Not.Null); - Assert.That(sut.HasIdentity, Is.True); + Assert.That(sut.HasIdentity, Is.True); Assert.That(sut.Properties.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); Assert.That(sut.Name, Is.EqualTo("Johnny Hefty")); Assert.That(sut.Email, Is.EqualTo("johnny@example.com")); @@ -350,7 +350,7 @@ namespace Umbraco.Tests.Persistence.Repositories { memberType = MockedContentTypes.CreateSimpleMemberType(); memberTypeRepository.AddOrUpdate(memberType); - unitOfWork.Commit(); + unitOfWork.Commit(); } var member = MockedMember.CreateSimpleMember(memberType, name ?? "Johnny Hefty", email ?? "johnny@example.com", password ?? "123", username ?? "hefty", key); From 5426c570fcd04215cc517ba6af4506b41e0527f1 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 2 Nov 2016 12:41:38 +0100 Subject: [PATCH 19/88] U4-9136 - group size 200 --- src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs | 2 +- src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs | 2 +- src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 70b968032b..608189a4bd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -173,7 +173,7 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of VersionableRepositoryBase - public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) + public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) { // the previous way of doing this was to run it all in one big transaction, // and to bulk-insert groups of xml rows - which works, until the transaction diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index d3356cd4f6..fdc92ce3af 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -163,7 +163,7 @@ namespace Umbraco.Core.Persistence.Repositories return media; } - public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) + public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) { // the previous way of doing this was to run it all in one big transaction, // and to bulk-insert groups of xml rows - which works, until the transaction diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 05f030f51d..66f803f9cd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -380,7 +380,7 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of VersionableRepositoryBase - public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) + public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) { // the previous way of doing this was to run it all in one big transaction, // and to bulk-insert groups of xml rows - which works, until the transaction From 0426f1f17de01c817c78e78fbe1e38def6478fa0 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 2 Nov 2016 14:24:17 +0100 Subject: [PATCH 20/88] Changes from Gerard Konings + a couple of small fixes --- .../upload/umbfiledropzone.directive.js | 86 +++++++++++++++---- .../services/mediatypehelper.service.js | 42 +++++++++ .../common/dialogs/mediapicker.controller.js | 14 ++- .../src/views/common/dialogs/mediapicker.html | 4 +- .../mediaPicker/mediapicker.controller.js | 11 ++- .../overlays/mediaPicker/mediapicker.html | 2 + .../mediapicktypepicker.controller.js | 10 +++ .../mediatypepicker/mediatypepicker.html | 15 ++++ .../components/upload/umb-file-dropzone.html | 7 ++ .../listview/layouts/grid/grid.html | 3 +- .../grid/grid.listviewlayout.controller.js | 10 ++- .../listview/layouts/list/list.html | 3 +- .../list/list.listviewlayout.controller.js | 37 +++++--- 13 files changed, 206 insertions(+), 38 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/overlays/mediatypepicker/mediapicktypepicker.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/overlays/mediatypepicker/mediatypepicker.html diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index e0678c1065..52704855bb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -25,7 +25,7 @@ TODO angular.module("umbraco.directives") -.directive('umbFileDropzone', function ($timeout, Upload, localizationService, umbRequestHelper) { +.directive('umbFileDropzone', function ($timeout, Upload, localizationService, umbRequestHelper, editorState) { return { restrict: 'E', @@ -42,6 +42,7 @@ angular.module("umbraco.directives") compact: '@', hideDropzone: '@', + acceptedMediatypes: '=', filesQueued: '=', handleFile: '=', @@ -49,14 +50,15 @@ angular.module("umbraco.directives") }, link: function(scope, element, attrs) { - + scope.queue = []; scope.done = []; scope.rejected = []; + scope.currentFile = undefined; function _filterFile(file) { - + var ignoreFileNames = ['Thumbs.db']; var ignoreFileTypes = ['directory']; @@ -85,20 +87,38 @@ angular.module("umbraco.directives") } else { scope.queue.push(file); } - } }); - + //when queue is done, kick the uploader if(!scope.working){ - _processQueueItem(); - } + + // Upload not allowed + if(!scope.acceptedMediatypes || !scope.acceptedMediatypes.length){ + files.map(function(file){ + file.uploadStatus = "error"; + file.serverErrorMessage = "File type is not allowed here"; + scope.rejected.push(file); + }); + scope.queue = []; + } + + // One allowed mediaType, pick this one + if(scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1){ + scope.contentTypeAlias = scope.acceptedMediatypes[0].alias; + _processQueueItem(); + } + + // More than one, open dialog + if(scope.acceptedMediatypes && scope.acceptedMediatypes.length > 1){ + _chooseMediaType(); + } + } } - function _processQueueItem(){ - + if(scope.queue.length > 0){ scope.currentFile = scope.queue.shift(); _upload(scope.currentFile); @@ -118,10 +138,10 @@ angular.module("umbraco.directives") } function _upload(file) { - + scope.propertyAlias = scope.propertyAlias ? scope.propertyAlias : "umbracoFile"; scope.contentTypeAlias = scope.contentTypeAlias ? scope.contentTypeAlias : "Image"; - + Upload.upload({ url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile"), fields: { @@ -204,18 +224,48 @@ angular.module("umbraco.directives") }); } + function _chooseMediaType() { + + scope.mediatypepickerOverlay = { + view: "mediatypepicker", + title: "Choose media type", + acceptedMediatypes: scope.acceptedMediatypes, + hideSubmitButton: true, + show: true, + submit: function(model) { + scope.contentTypeAlias = model.selectedType.alias; + + scope.mediatypepickerOverlay.show = false; + scope.mediatypepickerOverlay = null; + + _processQueueItem(); + }, + close: function(oldModel) { + + scope.queue.map(function(file){ + file.uploadStatus = "error"; + file.serverErrorMessage = "Cannot upload this file, no mediatype selected"; + scope.rejected.push(file); + }); + scope.queue = []; + + scope.mediatypepickerOverlay.show = false; + scope.mediatypepickerOverlay = null; + + } + }; + + } scope.handleFiles = function(files, event){ if(scope.filesQueued){ scope.filesQueued(files, event); } - - _filesQueued(files, event); + + _filesQueued(files, event); }; - } - - - }; - }); + } + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js new file mode 100644 index 0000000000..919a803456 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js @@ -0,0 +1,42 @@ +/** + * @ngdoc service + * @name umbraco.services.mediaTypeHelper + * @description A helper service for the media types + **/ +function mediaTypeHelper(mediaTypeResource, $q) { + + var mediaTypeHelperService = { + + getAllowedImagetypes: function (mediaId){ + + // Get All allowedTypes + return mediaTypeResource.getAllowedTypes(mediaId) + .then(function(types){ + + var allowedQ = types.map(function(type){ + return mediaTypeResource.getById(type.id); + }); + + // Get full list + return $q.all(allowedQ).then(function(fullTypes){ + // Only mediatypes with 'umbracoFile' property + + return fullTypes.filter(function(mediatype){ + for(var i = 0; i < mediatype.groups.length; i++){ + var group = mediatype.groups[i]; + for(var j = 0; j < group.properties.length; j++){ + var property = group.properties[j]; + if(property.editor === 'Umbraco.ImageCropper' || property.editor === 'Umbraco.UploadField'){ + return mediatype; + } + } + } + }); + }); + }); + } + }; + + return mediaTypeHelperService; +} +angular.module('umbraco.services').factory('mediaTypeHelper', mediaTypeHelper); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js index b29388be23..504f547182 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Dialogs.MediaPickerController", - function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, eventsService, treeService, $cookies, $element, $timeout) { + function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $cookies, $element, $timeout) { var dialogOptions = $scope.dialogOptions; @@ -11,13 +11,17 @@ angular.module("umbraco") $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; $scope.cropSize = dialogOptions.cropSize; - //preload selected item $scope.target = undefined; if(dialogOptions.currentTarget){ $scope.target = dialogOptions.currentTarget; } + $scope.acceptedMediatypes = []; + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId).then(function(types){ + $scope.acceptedMediatypes = types; + }); + $scope.upload = function(v){ angular.element(".umb-file-dropzone-directive .file-select").click(); }; @@ -65,6 +69,10 @@ angular.module("umbraco") return f.path.indexOf($scope.startNodeId) !== -1; }); }); + + mediaTypeHelper.getAllowedImagetypes(folder.id).then(function(types){ + $scope.acceptedMediatypes = types; + }); } else { $scope.path = []; @@ -76,7 +84,7 @@ angular.module("umbraco") $scope.searchTerm = ""; $scope.images = data.items ? data.items : []; }); - + $scope.currentFolder = folder; }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html index 540a19d64a..78d8e41127 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html @@ -116,8 +116,10 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html index 6a05d234f0..35d375e94d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html @@ -98,4 +98,11 @@ + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html index 4360996b55..16c5efe799 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -39,7 +39,8 @@ on-drag-enter="vm.dragEnter()"> Date: Mon, 31 Oct 2016 11:28:28 +0100 Subject: [PATCH 21/88] Backport SafeCallContext, DefaultDatabaseFactory fixes from 7.6 --- .../Packaging/PackageBinaryInspector.cs | 50 ++--- .../Persistence/DefaultDatabaseFactory.cs | 186 +++++++++++++----- src/Umbraco.Core/SafeCallContext.cs | 94 +++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Persistence/DatabaseContextTests.cs | 6 +- 5 files changed, 260 insertions(+), 77 deletions(-) create mode 100644 src/Umbraco.Core/SafeCallContext.cs diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs index 8852475543..116b5ee952 100644 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs @@ -29,20 +29,24 @@ namespace Umbraco.Core.Packaging /// public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport) { - var appDomain = GetTempAppDomain(); - var type = typeof(PackageBinaryInspector); - try + // beware! when toying with domains, use a safe call context! + using (new SafeCallContext()) { - var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( - type.Assembly.FullName, - type.FullName); - // do NOT turn PerformScan into static (even if ReSharper says so)! - var result = value.PerformScan(assemblys.ToArray(), out errorReport); - return result; - } - finally - { - AppDomain.Unload(appDomain); + var appDomain = GetTempAppDomain(); + var type = typeof(PackageBinaryInspector); + try + { + var value = (PackageBinaryInspector) appDomain.CreateInstanceAndUnwrap( + type.Assembly.FullName, + type.FullName); + // do NOT turn PerformScan into static (even if ReSharper says so)! + var result = value.PerformScan(assemblys.ToArray(), out errorReport); + return result; + } + finally + { + AppDomain.Unload(appDomain); + } } } @@ -78,7 +82,7 @@ namespace Umbraco.Core.Packaging /// /// Performs the assembly scanning /// - /// + /// /// /// /// @@ -107,7 +111,7 @@ namespace Umbraco.Core.Packaging /// /// Performs the assembly scanning /// - /// + /// /// /// /// @@ -154,7 +158,7 @@ namespace Umbraco.Core.Packaging //get the list of assembly names to compare below var loadedNames = loaded.Select(x => x.GetName().Name).ToArray(); - + //Then load each referenced assembly into the context foreach (var a in loaded) { @@ -170,7 +174,7 @@ namespace Umbraco.Core.Packaging } catch (FileNotFoundException) { - //if an exception occurs it means that a referenced assembly could not be found + //if an exception occurs it means that a referenced assembly could not be found errors.Add( string.Concat("This package references the assembly '", assemblyName.Name, @@ -179,7 +183,7 @@ namespace Umbraco.Core.Packaging } catch (Exception ex) { - //if an exception occurs it means that a referenced assembly could not be found + //if an exception occurs it means that a referenced assembly could not be found errors.Add( string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '", assemblyName.Name, @@ -197,7 +201,7 @@ namespace Umbraco.Core.Packaging { //now we need to see if they contain any type 'T' var reflectedAssembly = a; - + try { var found = reflectedAssembly.GetExportedTypes() @@ -210,8 +214,8 @@ namespace Umbraco.Core.Packaging } catch (Exception ex) { - //This is a hack that nobody can seem to get around, I've read everything and it seems that - // this is quite a common thing when loading types into reflection only load context, so + //This is a hack that nobody can seem to get around, I've read everything and it seems that + // this is quite a common thing when loading types into reflection only load context, so // we're just going to ignore this specific one for now var typeLoadEx = ex as TypeLoadException; if (typeLoadEx != null) @@ -232,7 +236,7 @@ namespace Umbraco.Core.Packaging LogHelper.Error("An error occurred scanning package assemblies", ex); } } - + } errorReport = errors.ToArray(); @@ -252,7 +256,7 @@ namespace Umbraco.Core.Packaging var contractType = contractAssemblyLoadFrom.GetExportedTypes() .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName); - + if (contractType == null) { throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies"); diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 658b8ebabe..c96b442e6e 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -1,6 +1,6 @@ using System; +using System.Runtime.Remoting.Messaging; using System.Web; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; namespace Umbraco.Core.Persistence @@ -19,13 +19,24 @@ namespace Umbraco.Core.Persistence private readonly ILogger _logger; public string ConnectionString { get; private set; } public string ProviderName { get; private set; } - - //very important to have ThreadStatic: - // see: http://issues.umbraco.org/issue/U4-2172 - [ThreadStatic] - private static volatile UmbracoDatabase _nonHttpInstance; - private static readonly object Locker = new object(); + // NO! see notes in v8 HybridAccessorBase + //[ThreadStatic] + //private static volatile UmbracoDatabase _nonHttpInstance; + + private const string ItemKey = "Umbraco.Core.Persistence.DefaultDatabaseFactory"; + + private static UmbracoDatabase NonContextValue + { + get { return (UmbracoDatabase) CallContext.LogicalGetData(ItemKey); } + set + { + if (value == null) CallContext.FreeNamedDataSlot(ItemKey); + else CallContext.LogicalSetData(ItemKey, value); + } + } + + private static readonly object Locker = new object(); /// /// Constructor accepting custom connection string @@ -36,7 +47,10 @@ namespace Umbraco.Core.Persistence { if (logger == null) throw new ArgumentNullException("logger"); Mandate.ParameterNotNullOrEmpty(connectionStringName, "connectionStringName"); - _connectionStringName = connectionStringName; + + //if (NonContextValue != null) throw new Exception("NonContextValue is not null."); + + _connectionStringName = connectionStringName; _logger = logger; } @@ -51,65 +65,133 @@ namespace Umbraco.Core.Persistence if (logger == null) throw new ArgumentNullException("logger"); Mandate.ParameterNotNullOrEmpty(connectionString, "connectionString"); Mandate.ParameterNotNullOrEmpty(providerName, "providerName"); - ConnectionString = connectionString; + + //if (NonContextValue != null) throw new Exception("NonContextValue is not null."); + + ConnectionString = connectionString; ProviderName = providerName; _logger = logger; } public UmbracoDatabase CreateDatabase() { - //no http context, create the singleton global object - if (HttpContext.Current == null) - { - if (_nonHttpInstance == null) - { - lock (Locker) - { - //double check - if (_nonHttpInstance == null) - { - _nonHttpInstance = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false - ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) - : new UmbracoDatabase(_connectionStringName, _logger); - } - } - } - return _nonHttpInstance; - } + // no http context, create the call context object + // NOTHING is going to track the object and it is the responsibility of the caller to release it! + // using the ReleaseDatabase method. + if (HttpContext.Current == null) + { + LogHelper.Debug("Get NON http [T" + Environment.CurrentManagedThreadId + "]"); + var value = NonContextValue; + if (value != null) return value; + lock (Locker) + { + value = NonContextValue; + if (value != null) return value; - //we have an http context, so only create one per request - if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false) - { - HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory), - string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false + LogHelper.Debug("Create NON http [T" + Environment.CurrentManagedThreadId + "]"); + NonContextValue = value = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false + ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) + : new UmbracoDatabase(_connectionStringName, _logger); + + return value; + } + } + + // we have an http context, so only create one per request. + // UmbracoDatabase is marked IDisposeOnRequestEnd and therefore will be disposed when + // UmbracoModule attempts to dispose the relevant HttpContext items. so we DO dispose + // connections at the end of each request. no need to call ReleaseDatabase. + LogHelper.Debug("Get http [T" + Environment.CurrentManagedThreadId + "]"); + if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false) + { + LogHelper.Debug("Create http [T" + Environment.CurrentManagedThreadId + "]"); + HttpContext.Current.Items.Add(typeof(DefaultDatabaseFactory), + string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) : new UmbracoDatabase(_connectionStringName, _logger)); - } - return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; - } + } + return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; + } - protected override void DisposeResources() + // releases the "context" database + public void ReleaseDatabase() + { + if (HttpContext.Current == null) + { + var value = NonContextValue; + if (value != null) value.Dispose(); + NonContextValue = null; + } + else + { + var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; + if (db != null) db.Dispose(); + HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory)); + } + } + + protected override void DisposeResources() { - if (HttpContext.Current == null) - { - _nonHttpInstance.Dispose(); - } - else - { - if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory))) - { - ((UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]).Dispose(); - } - } + ReleaseDatabase(); } // during tests, the thread static var can leak between tests // this method provides a way to force-reset the variable internal void ResetForTests() { - if (_nonHttpInstance == null) return; - _nonHttpInstance.Dispose(); - _nonHttpInstance = null; - } - } + var value = NonContextValue; + if (value != null) value.Dispose(); + NonContextValue = null; + } + + #region SafeCallContext + + // see notes in SafeCallContext - need to do this since we are using + // the logical call context... + + static DefaultDatabaseFactory() + { + SafeCallContext.Register(DetachDatabase, AttachDatabase); + } + + // detaches the current database + // ie returns the database and remove it from whatever is "context" + private static UmbracoDatabase DetachDatabase() + { + if (HttpContext.Current == null) + { + var db = NonContextValue; + NonContextValue = null; + return db; + } + else + { + var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; + HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory)); + return db; + } + } + + // attach a current database + // ie assign it to whatever is "context" + // throws if there already is a database + private static void AttachDatabase(object o) + { + var database = o as UmbracoDatabase; + if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o"); + + if (HttpContext.Current == null) + { + if (NonContextValue != null) throw new InvalidOperationException(); + if (database != null) NonContextValue = database; + } + else + { + if (HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] != null) throw new InvalidOperationException(); + if (database != null) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] = database; + } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Core/SafeCallContext.cs b/src/Umbraco.Core/SafeCallContext.cs new file mode 100644 index 0000000000..5ed41d389f --- /dev/null +++ b/src/Umbraco.Core/SafeCallContext.cs @@ -0,0 +1,94 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +namespace Umbraco.Core +{ + internal class SafeCallContext : IDisposable + { + private static readonly List> EnterFuncs = new List>(); + private static readonly List> ExitActions = new List>(); + private static int _count; + private readonly List _objects; + private bool _disposed; + + public static void Register(Func enterFunc, Action exitAction) + { + if (enterFunc == null) throw new ArgumentNullException("enterFunc"); + if (exitAction == null) throw new ArgumentNullException("exitAction"); + + lock (EnterFuncs) + { + if (_count > 0) throw new InvalidOperationException("Cannot register while some SafeCallContext instances exist."); + EnterFuncs.Add(enterFunc); + ExitActions.Add(exitAction); + } + } + + // tried to make the UmbracoDatabase serializable but then it leaks to weird places + // in ReSharper and so on, where Umbraco.Core is not available. Tried to serialize + // as an object instead but then it comes *back* deserialized into the original context + // as an object and of course it breaks everything. Cannot prevent this from flowing, + // and ExecutionContext.SuppressFlow() works for threads but not domains. and we'll + // have the same issue with anything that toys with logical call context... + // + // so this class lets anything that uses the logical call context register itself, + // providing two methods: + // - an enter func that removes and returns whatever is in the logical call context + // - an exit action that restores the value into the logical call context + // whenever a SafeCallContext instance is created, it uses these methods to capture + // and clear the logical call context, and restore it when disposed. + // + // in addition, a static Clear method is provided - which uses the enter funcs to + // remove everything from logical call context - not to be used when the app runs, + // but can be useful during tests + // + // note + // see System.Transactions + // they are using a conditional weak table to store the data, and what they store in + // LLC is the key - which is just an empty MarshalByRefObject that is created with + // the transaction scope - that way, they can "clear current data" provided that + // they have the key - but they need to hold onto a ref to the scope... not ok for us + + public static void Clear() + { + lock (EnterFuncs) + { + foreach (var enter in EnterFuncs) + enter(); + } + } + + public SafeCallContext() + { + lock (EnterFuncs) + { + _count++; + _objects = EnterFuncs.Select(x => x()).ToList(); + } + } + + public void Dispose() + { + if (_disposed) throw new ObjectDisposedException("this"); + _disposed = true; + lock (EnterFuncs) + { + for (var i = 0; i < ExitActions.Count; i++) + ExitActions[i](_objects[i]); + _count--; + } + } + + // for unit tests ONLY + internal static void Reset() + { + lock (EnterFuncs) + { + if (_count > 0) throw new InvalidOperationException("Cannot reset while some SafeCallContext instances exist."); + EnterFuncs.Clear(); + ExitActions.Clear(); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c28cea24cd..9e323b0c7e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -504,6 +504,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 94f0010201..a9b016c2b6 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -23,6 +23,8 @@ namespace Umbraco.Tests.Persistence [SetUp] public void Setup() { + SafeCallContext.Clear(); + _dbContext = new DatabaseContext( new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Mock.Of()), Mock.Of(), new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe); @@ -34,7 +36,7 @@ namespace Umbraco.Tests.Persistence { DatabaseContext = _dbContext, IsReady = true - }; + }; } [TearDown] @@ -99,7 +101,7 @@ namespace Umbraco.Tests.Persistence var appCtx = new ApplicationContext( new DatabaseContext(Mock.Of(), Mock.Of(), Mock.Of(), "test"), - new ServiceContext(migrationEntryService: Mock.Of()), + new ServiceContext(migrationEntryService: Mock.Of()), CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of())); From 07f4da9b007c5d40909f83577bf9b5c8b7e110f2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 2 Nov 2016 09:42:20 +0100 Subject: [PATCH 22/88] Cleanup WebProfiler from 8.x --- .../Profiling/StartupWebProfilerProvider.cs | 159 ------------------ src/Umbraco.Web/Profiling/WebProfiler.cs | 72 ++++---- .../Profiling/WebProfilerProvider.cs | 121 +++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 4 files changed, 154 insertions(+), 200 deletions(-) delete mode 100644 src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs create mode 100644 src/Umbraco.Web/Profiling/WebProfilerProvider.cs diff --git a/src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs b/src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs deleted file mode 100644 index 72a398f17f..0000000000 --- a/src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System.Threading; -using System.Web; -using StackExchange.Profiling; -using Umbraco.Core; - -namespace Umbraco.Web.Profiling -{ - /// - /// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows - /// us to profile items during app startup - before an HttpRequest is created - /// - /// - /// Once the boot phase is changed to StartupPhase.Request then the base class (default) provider will handle all - /// profiling data and this sub class no longer performs any logic. - /// - internal class StartupWebProfilerProvider : WebRequestProfilerProvider - { - public StartupWebProfilerProvider() - { - _startupPhase = StartupPhase.Boot; - //create the startup profiler - _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup", ProfileLevel.Verbose) - { - Name = "StartupProfiler" - }; - } - - private MiniProfiler _startupProfiler; - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); - - /// - /// Used to determine which phase the boot process is in - /// - private enum StartupPhase - { - None = 0, - Boot = 1, - Request = 2 - } - - private volatile StartupPhase _startupPhase; - - /// - /// Executed once the application boot process is complete and changes the phase to Request - /// - public void BootComplete() - { - using (new ReadLock(_locker)) - { - if (_startupPhase != StartupPhase.Boot) return; - } - - using (var l = new UpgradeableReadLock(_locker)) - { - if (_startupPhase == StartupPhase.Boot) - { - l.UpgradeToWriteLock(); - _startupPhase = StartupPhase.Request; - } - } - } - - /// - /// Executed when a profiling operation is completed - /// - /// - /// - /// This checks if the bootup phase is None, if so, it just calls the base class, otherwise it checks - /// if a profiler is active (i.e. in startup), then sets the phase to None so that the base class will be used - /// for all subsequent calls. - /// - public override void Stop(bool discardResults) - { - using (new ReadLock(_locker)) - { - if (_startupPhase == StartupPhase.None) - { - base.Stop(discardResults); - return; - } - } - - using (var l = new UpgradeableReadLock(_locker)) - { - if (_startupPhase > 0 && base.GetCurrentProfiler() == null) - { - l.UpgradeToWriteLock(); - - _startupPhase = StartupPhase.None; - - //This is required to pass the mini profiling context from before a request - // to the current startup request. - if (HttpContext.Current != null) - { - HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; - base.Stop(discardResults); - _startupProfiler = null; - } - } - else - { - base.Stop(discardResults); - } - } - } - - /// - /// Executed when a profiling operation is started - /// - /// - /// - /// - /// This checks if the startup phase is not None, if this is the case and the current profiler is NULL - /// then this sets the startup profiler to be active. Otherwise it just calls the base class Start method. - /// - public override MiniProfiler Start(ProfileLevel level) - { - using (new ReadLock(_locker)) - { - if (_startupPhase > 0 && base.GetCurrentProfiler() == null) - { - SetProfilerActive(_startupProfiler); - return _startupProfiler; - } - - return base.Start(level); - } - } - - /// - /// This returns the current profiler - /// - /// - /// - /// If the boot phase is not None, then this will return the startup profiler (this), otherwise - /// returns the base class - /// - public override MiniProfiler GetCurrentProfiler() - { - using (new ReadLock(_locker)) - { - if (_startupPhase > 0) - { - try - { - var current = base.GetCurrentProfiler(); - if (current == null) return _startupProfiler; - } - catch - { - return _startupProfiler; - } - } - - return base.GetCurrentProfiler(); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Profiling/WebProfiler.cs b/src/Umbraco.Web/Profiling/WebProfiler.cs index a1998c8761..62d69019d6 100644 --- a/src/Umbraco.Web/Profiling/WebProfiler.cs +++ b/src/Umbraco.Web/Profiling/WebProfiler.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Web; using StackExchange.Profiling; using StackExchange.Profiling.SqlFormatters; @@ -14,22 +15,23 @@ namespace Umbraco.Web.Profiling /// internal class WebProfiler : IProfiler { - private StartupWebProfilerProvider _startupWebProfilerProvider; + private const string BootRequestItemKey = "Umbraco.Web.Profiling.WebProfiler__isBootRequest"; + private WebProfilerProvider _provider; + private int _first; /// /// Constructor /// internal WebProfiler() { - //setup some defaults + // create our own provider, which can provide a profiler even during boot + // MiniProfiler's default cannot because there's no HttpRequest in HttpContext + _provider = new WebProfilerProvider(); + + // settings MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); MiniProfiler.Settings.StackMaxLength = 5000; - - //At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext - // since it hasn't started yet. So we need to do some hacking to enable profiling during startup. - _startupWebProfilerProvider = new StartupWebProfilerProvider(); - //this should always be the case during startup, we'll need to set a custom profiler provider - MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider; + MiniProfiler.Settings.ProfilerProvider = _provider; //Binds to application events to enable the MiniProfiler with a real HttpRequest UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; @@ -57,43 +59,33 @@ namespace Umbraco.Web.Profiling } } - /// - /// Handle the begin request event - /// - /// - /// - void UmbracoApplicationEndRequest(object sender, EventArgs e) - { - if (_startupWebProfilerProvider != null) - { - Stop(); - _startupWebProfilerProvider = null; - } - else if (CanPerformProfilingAction(sender)) - { - Stop(); - } - } - - /// - /// Handle the end request event - /// - /// - /// void UmbracoApplicationBeginRequest(object sender, EventArgs e) { - if (_startupWebProfilerProvider != null) + // if this is the first request, notify our own provider that this request is the boot request + var first = Interlocked.Exchange(ref _first, 1) == 0; + if (first) { - _startupWebProfilerProvider.BootComplete(); + _provider.BeginBootRequest(); + ((HttpApplication)sender).Context.Items[BootRequestItemKey] = true; + // and no need to start anything, profiler is already there } - - if (CanPerformProfilingAction(sender)) - { + // else start a profiler, the normal way + else if (ShouldProfile(sender)) Start(); - } } - private bool CanPerformProfilingAction(object sender) + void UmbracoApplicationEndRequest(object sender, EventArgs e) + { + // if this is the boot request, or if we should profile this request, stop + // (the boot request is always profiled, no matter what) + var isBootRequest = ((HttpApplication)sender).Context.Items[BootRequestItemKey] != null; // fixme perfs + if (isBootRequest) + _provider.EndBootRequest(); + if (isBootRequest || ShouldProfile(sender)) + Stop(); + } + + private bool ShouldProfile(object sender) { if (GlobalSettings.DebugMode == false) return false; @@ -108,10 +100,10 @@ namespace Umbraco.Web.Profiling return false; if (string.IsNullOrEmpty(request.Result.QueryString["umbDebug"])) - return true; + return false; if (request.Result.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) - return true; + return false; return true; } diff --git a/src/Umbraco.Web/Profiling/WebProfilerProvider.cs b/src/Umbraco.Web/Profiling/WebProfilerProvider.cs new file mode 100644 index 0000000000..ffd1871ecc --- /dev/null +++ b/src/Umbraco.Web/Profiling/WebProfilerProvider.cs @@ -0,0 +1,121 @@ +using System; +using System.Threading; +using System.Web; +using StackExchange.Profiling; + +namespace Umbraco.Web.Profiling +{ + /// + /// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows + /// us to profile items during app startup - before an HttpRequest is created + /// + /// + /// Once the boot phase is changed to StartupPhase.Request then the base class (default) provider will handle all + /// profiling data and this sub class no longer performs any logic. + /// + internal class WebProfilerProvider : WebRequestProfilerProvider + { + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + private MiniProfiler _startupProfiler; + private int _first; + private volatile BootPhase _bootPhase; + + public WebProfilerProvider() + { + // booting... + _bootPhase = BootPhase.Boot; + } + + /// + /// Indicates the boot phase. + /// + private enum BootPhase + { + Boot = 0, // boot phase (before the 1st request even begins) + BootRequest = 1, // request boot phase (during the 1st request) + Booted = 2 // done booting + } + + public void BeginBootRequest() + { + _locker.EnterWriteLock(); + try + { + if (_bootPhase != BootPhase.Boot) + throw new InvalidOperationException("Invalid boot phase."); + _bootPhase = BootPhase.BootRequest; + + // assign the profiler to be the current MiniProfiler for the request + // is's already active, starting and all + HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; + } + finally + { + _locker.ExitWriteLock(); + } + } + + public void EndBootRequest() + { + _locker.EnterWriteLock(); + try + { + if (_bootPhase != BootPhase.BootRequest) + throw new InvalidOperationException("Invalid boot phase."); + _bootPhase = BootPhase.Booted; + + _startupProfiler = null; + } + finally + { + _locker.ExitWriteLock(); + } + } + + /// + /// Executed when a profiling operation is started + /// + /// + /// + /// + /// This checks if the startup phase is not None, if this is the case and the current profiler is NULL + /// then this sets the startup profiler to be active. Otherwise it just calls the base class Start method. + /// + public override MiniProfiler Start(ProfileLevel level) + { + var first = Interlocked.Exchange(ref _first, 1) == 0; + if (first == false) return base.Start(level); + + _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup") { Name = "StartupProfiler" }; + SetProfilerActive(_startupProfiler); + return _startupProfiler; + } + + /// + /// This returns the current profiler + /// + /// + /// + /// If the boot phase is not None, then this will return the startup profiler (this), otherwise + /// returns the base class + /// + public override MiniProfiler GetCurrentProfiler() + { + // if not booting then just use base (fast) + // no lock, _bootPhase is volatile + if (_bootPhase == BootPhase.Booted) + return base.GetCurrentProfiler(); + + // else + try + { + var current = base.GetCurrentProfiler(); + return current ?? _startupProfiler; + } + catch + { + return _startupProfiler; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7fb022e848..527d96dcea 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -382,7 +382,7 @@ - + From 5a5be024e4ce6fdacd3041a91627e65c2478f7c0 Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Thu, 3 Nov 2016 00:12:44 +0700 Subject: [PATCH 23/88] Update ru.xml UI language file Dictionary item editor keys added --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 75c5630678..d71b4423aa 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -380,6 +380,12 @@ Ниже Вы можете указать различные переводы данной статьи словаря '%0%'
Добавить другие языки можно, воспользовавшись пунктом 'Языки' в меню слева ]]> Название языка (культуры) + Редактировать элемент (ключ) словаря + + + Допустим как корневой @@ -655,7 +661,7 @@ Медиа - всего в XML: %0%, всего: %1%Б с ошибками: %2% Содержимое - всего в XML: %0%, всего опубликовано: %1%, с ошибками: %2% - Сертификат Вашего сайта отмечен как проверенный. + Сертификат Вашего веб-сайта отмечен как проверенный. Ошибка проверки сертификата: '%0%' Ошибка проверки адреса URL %0% - '%1%' Сейчас Вы %0% просматриваете сайт, используя протокол HTTPS. From a5b0fbb83dfdd9f46391d4374425e26b67aa8b6c Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Wed, 2 Nov 2016 19:51:54 +0100 Subject: [PATCH 24/88] Headline in the "Republish entire site" dialog is now translated --- src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 41f4ae232a..1d142158e3 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -254,7 +254,7 @@ namespace Umbraco.Web.Trees return Attempt.Succeed( new LegacyUrlAction( "dialogs/republish.aspx?rnd=" + DateTime.UtcNow.Ticks, - "Republishing entire site")); + ui.GetText("actions", "republish"))); case "UmbClientMgr.appActions().actionAssignDomain()": return Attempt.Succeed( new LegacyUrlAction( @@ -415,4 +415,4 @@ namespace Umbraco.Web.Trees } } -} \ No newline at end of file +} From 030708194ab579cfde58fa4ba8dd95e390baa09b Mon Sep 17 00:00:00 2001 From: Rasmus Fjord Date: Wed, 2 Nov 2016 20:15:55 +0100 Subject: [PATCH 25/88] Oversat publish events --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 192 ++++++++++-------- 1 file changed, 108 insertions(+), 84 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index ffb0a9d96b..6ac936533b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -202,12 +202,12 @@ Færdig - + Slettede %0% element Slettede %0% elementer Slettede %0% ud af %1% element Slettede %0% ud af %1% elementer - + Udgav %0% element Udgav %0% elementer Udgav %0% ud af %1% element @@ -293,20 +293,22 @@ Vælg indhold Vælg medlem Vælg medlemsgruppe - + Der er ingen parametre for denne makro Link dit - Fjern link fra dit - + Fjern link fra dit + konto - + Vælg editor - + Du tilføjer flere sprog under 'sprog' i menuen til venstre - ]]> + ]]> + Kulturnavn Rediger navnet på ordbogselementet. @@ -327,17 +329,17 @@ Indtast nøgleord (tryk på Enter efter hvert nøgleord)... - Tillad på rodniveau + Tillad på rodniveau Kun dokumenttyper med denne indstilling aktiveret oprettes i rodniveau under Inhold og Mediearkiv Tilladte typer - Sammensætning af dokumenttyper + Sammensætning af dokumenttyper Opret Slet fane Beskrivelse Ny fane Fane Thumbnail - Aktiver listevisning + Aktiver listevisning Viser undersider i en søgbar liste, undersider vises ikke i indholdstræet Nuværende listevisning Den aktive listevisningsdatatype @@ -372,7 +374,7 @@ Der skete en fejl på severen - Denne filttype er blevet deaktiveret af administratoren + Denne filttype er blevet deaktiveret af administratoren OBS! Selvom CodeMirror er slået til i konfigurationen, så er den deaktiveret i Internet Explorer fordi den ikke er stabil nok. Du skal udfylde både Alias & Navn på den nye egenskabstype! Der mangler læse/skrive rettigheder til bestemte filer og mapper @@ -388,7 +390,7 @@ Du kan ikke opdele en celle, som ikke allerede er delt. Fejl i XSLT kode Din XSLT er ikke opdateret, da det indeholdt en fejl - Der er et problem med den datatype, der bruges til denn egenskab. Kontroller konfigurationen og prøv igen. + Der er et problem med den datatype, der bruges til denn egenskab. Kontroller konfigurationen og prøv igen. Om @@ -474,7 +476,8 @@ Hvilken side skal vises efter at formularen er sendt Størrelse Sortér - Indsend + Indsend + Type Skriv for at søge... Op @@ -513,22 +516,22 @@ - Tilføj fane - Tilføj egenskab - Tilføj editor - Tilføj skabelon - Tilføj child node - Tilføj child + Tilføj fane + Tilføj egenskab + Tilføj editor + Tilføj skabelon + Tilføj child node + Tilføj child - Rediger datatype + Rediger datatype - Naviger sektioner + Naviger sektioner - Genveje - Vis genveje + Genveje + Vis genveje - Brug listevisning - Tillad på rodniveau + Brug listevisning + Tillad på rodniveau @@ -546,13 +549,17 @@ Kunne ikke gemme web.config filen. Du bedes venligst manuelt ændre database forbindelses strengen. Din database er blevet fundet og identificeret som Database konfiguration - + installér knappen for at installere Umbraco %0% databasen - ]]> + ]]> + installér knappen for at installere Umbraco %0% databasen]]> Næste for at fortsætte.]]> - Databasen er ikke fundet. Kontrollér venligst at informationen i database forbindelsesstrengen i "web.config" filen er korrekt.

-

For at fortsætte bedes du venligst rette "web.config" filen (ved at bruge Visual Studio eller dit favoritprogram), scroll til bunden, tilføj forbindelsesstrengen til din database i feltet som hedder "umbracoDbDSN" og gem filen.

Klik på Forsøg igen knappen når du er færdig.
Mere information om at redigere web.config her.

]]>
+ + Databasen er ikke fundet. Kontrollér venligst at informationen i database forbindelsesstrengen i "web.config" filen er korrekt.

+

For at fortsætte bedes du venligst rette "web.config" filen (ved at bruge Visual Studio eller dit favoritprogram), scroll til bunden, tilføj forbindelsesstrengen til din database i feltet som hedder "umbracoDbDSN" og gem filen.

Klik på Forsøg igen knappen når du er færdig.
Mere information om at redigere web.config her.

]]> +
Kontakt venligst din ISP hvis det er nødvendigt. Hvis du installerer på en lokal maskine eller server kan du muligvis få informationerne fra din systemadministrator.]]> Tryk på Opgradér knappen for at opgradere din database til Umbraco %0%

Bare rolig - intet indhold vil blive slettet og alt vil stadig fungere bagefter!

]]>
Tryk på Næste for at fortsætte.]]> @@ -598,8 +605,10 @@ Yderligere hjælpe og informationer Få hjælp fra vores prisvindende fællesskab, gennemse dokumentationen eller se nogle gratis videoer om hvordan du opsætter et simpelt site, hvordan du bruger pakker og en 'quick guide' til Umbraco terminologier]]> Umbraco %0% er installeret og klar til brug /web.config filen og opdatére 'AppSetting' feltet UmbracoConfigurationStatus i bunden til '%0%'.]]> - komme igang med det samme ved at klikke på "Start Umbraco" knappen nedenfor.
Hvis du er ny med Umbraco, kan du finde masser af ressourcer på vores 'getting started' sider. -]]>
+ + komme igang med det samme ved at klikke på "Start Umbraco" knappen nedenfor.
Hvis du er ny med Umbraco, kan du finde masser af ressourcer på vores 'getting started' sider. +]]> +
Start UmbracoFor at administrere dit website skal du blot åbne Umbraco administrationen og begynde at tilføje indhold, opdatere skabelonerne og stylesheets'ene eller tilføje ny funktionalitet.]]> Forbindelse til databasen fejlede. Umbraco Version 3 @@ -674,12 +683,15 @@ Gå til http://%4%/#/content/content/edit/%5% for at redigere. Ha' en dejlig dag! Mange hilsner fra Umbraco robotten - ]]> - Hej %0%

+ ]]> +
+ + Hej %0%

Dette er en automatisk mail for at informere dig om at opgaven '%1%' er blevet udførtpå siden '%2%' af brugeren '%3%'

Opdateringssammendrag:

%6%

Hav en fortsat god dag!

De bedste hilsner fra umbraco robotten

]]>
+      RET       

Opdateringssammendrag:

%6%

Hav en fortsat god dag!

De bedste hilsner fra umbraco robotten

]]> + [%0%] Notificering om %1% udført på %2% Notificeringer @@ -700,8 +712,10 @@ Mange hilsner fra Umbraco robotten Pakken blev fjernet Pakken er på succefuld vis blevet fjernet Afinstallér pakke - -Bemærk: at dokumenter og medier som afhænger af denne pakke vil muligvis holde op med at virke, så vær forsigtig. Hvis i tvivl, kontakt personen som har udviklet pakken.]]> + + +Bemærk: at dokumenter og medier som afhænger af denne pakke vil muligvis holde op med at virke, så vær forsigtig. Hvis i tvivl, kontakt personen som har udviklet pakken.]]> + Download opdatering fra opbevaringsbasen Opdatér pakke Opdateringsinstrukser @@ -734,6 +748,19 @@ Mange hilsner fra Umbraco robotten Hvis du blot ønsker at opsætte simpel beskyttelse ved hjælp af et enkelt login og kodeord + Udgivelsen kunne ikke udgives da publiceringsdato er sat + + + + + + + Udgivelsen fejlede fordi en overordnet side ikke er publiceret + %0% kunne ikke udgives, fordi et 3. parts modul annullerede handlingen Medtag ikke-udgivede undersider Publicerer - vent venligst... @@ -807,58 +834,58 @@ Mange hilsner fra Umbraco robotten - Kompositioner - Du har ikke tilføjet nogle faner - Tilføj ny fane - Tilføj endnu en fane - Nedarvet fra - Tilføj property - Påkrævet label + Kompositioner + Du har ikke tilføjet nogle faner + Tilføj ny fane + Tilføj endnu en fane + Nedarvet fra + Tilføj property + Påkrævet label - Aktiver listevisning - Konfigurer indholdet til at blive vist i en sorterbar og søgbar liste, dens børn vil ikke blive vist i træet + Aktiver listevisning + Konfigurer indholdet til at blive vist i en sorterbar og søgbar liste, dens børn vil ikke blive vist i træet - Tilladte skabeloner - Vælg hvilke skabeloner der er tilladt at bruge på dette indhold + Tilladte skabeloner + Vælg hvilke skabeloner der er tilladt at bruge på dette indhold - Tillad på rodniveau - Kun dokumenttyper med denne indstilling aktiveret oprettes i rodniveau under inhold og mediearkiv - Ja – indhold af denne type er tilladt i roden + Tillad på rodniveau + Kun dokumenttyper med denne indstilling aktiveret oprettes i rodniveau under inhold og mediearkiv + Ja – indhold af denne type er tilladt i roden - Tilladte typer - Tillad at oprette indhold af en specifik type under denne + Tilladte typer + Tillad at oprette indhold af en specifik type under denne - Vælg child node + Vælg child node - Nedarv faner og egenskaber fra en anden dokumenttype. Nye faner vil blive tilføjet den nuværende dokumenttype eller sammenflettet hvis fanenavnene er ens. - Indholdstypen bliver brugt i en komposition og kan derfor ikke blive anvendt som komposition - Der er ingen indholdstyper tilgængelige at bruge som komposition + Nedarv faner og egenskaber fra en anden dokumenttype. Nye faner vil blive tilføjet den nuværende dokumenttype eller sammenflettet hvis fanenavnene er ens. + Indholdstypen bliver brugt i en komposition og kan derfor ikke blive anvendt som komposition + Der er ingen indholdstyper tilgængelige at bruge som komposition - Tilgængelige editors - Genbrug - Editor indstillinger + Tilgængelige editors + Genbrug + Editor indstillinger - Konfiguration + Konfiguration - Ja, slet + Ja, slet - blev flyttet til - Vælg hvor - skal flyttes til + blev flyttet til + Vælg hvor + skal flyttes til - Alle dokumenttyper - Alle dokumenter - Alle medier + Alle dokumenttyper + Alle dokumenter + Alle medier - som benytter denne dokumenttype vil blive slettet permanent. Bekræft at du også vil slette dem. - som benytter denne medietype vil blive slettet permanent. Bekræft at du også vil slette dem. - som benytter denne medlemstype vil blive slettet permanent. Bekræft at du også vil slette dem. + som benytter denne dokumenttype vil blive slettet permanent. Bekræft at du også vil slette dem. + som benytter denne medietype vil blive slettet permanent. Bekræft at du også vil slette dem. + som benytter denne medlemstype vil blive slettet permanent. Bekræft at du også vil slette dem. - og alle dokumenter, som benytter denne type - og alle medier, som benytter denne type - og alle medlemmer, som benytter denne type + og alle dokumenter, som benytter denne type + og alle medier, som benytter denne type + og alle medlemmer, som benytter denne type - der bruger denne editor vil blive opdateret med de nye indstillinger + der bruger denne editor vil blive opdateret med de nye indstillinger @@ -922,8 +949,6 @@ Mange hilsner fra Umbraco robotten Annulleret Handlingen blev annulleret af et 3. part tilføjelsesprogram - Udgivelsen blev standset af et 3. parts modul - Udgivelsen kunne ikke udgives da publiceringsdato er sat Property type eksisterer allerede Egenskabstype oprettet DataType: %1%]]> @@ -937,7 +962,6 @@ Mange hilsner fra Umbraco robotten Stylesheet gemt uden fejl Datatype gemt Ordbogsnøgle gemt - Udgivelsen fejlede fordi en overordnet side ikke er publiceret Indhold publiceret og nu synligt for besøgende Indhold gemt @@ -1139,11 +1163,11 @@ Mange hilsner fra Umbraco robotten Session udløber - Validation - Valider som email - Valider som tal - Valider som Url - ...eller indtast din egen validering - Feltet er påkrævet + Validation + Valider som email + Valider som tal + Valider som Url + ...eller indtast din egen validering + Feltet er påkrævet From 0624d224a9a9a93021c4b7e847be0a7bb4d1ae8b Mon Sep 17 00:00:00 2001 From: Rasmus Fjord Date: Wed, 2 Nov 2016 20:30:05 +0100 Subject: [PATCH 26/88] Translated set permissions windows --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 1 + .../umbraco.presentation/umbraco/dialogs/cruds.aspx.cs | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 6ac936533b..ad05703939 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -30,6 +30,7 @@ Genindlæs elementer Genudgiv hele sitet Gendan + Sæt rettigheder for siden %0% Rettigheder Fortryd ændringer Send til udgivelse diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f710e54215..d1624d75d4 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -28,6 +28,7 @@ Reload Republish entire site Restore + Set permissions for the page %0% Permissions Rollback Send To Publish diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 80c6faf07b..8d5348bc87 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -27,6 +27,7 @@ Unpublish Reload Republish entire site + Set permissions for the page %0% Restore Permissions Rollback diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs index 1634fd370f..3808547db1 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs @@ -34,7 +34,7 @@ namespace umbraco.dialogs protected void Page_Load(object sender, EventArgs e) { Button1.Text = ui.Text("update"); - pane_form.Text = "Set permissions for the page " + _node.Text; + pane_form.Text = ui.Text("actions", "SetPermissionsForThePage",_node.Text); } override protected void OnInit(EventArgs e) From e015e73abe4793d69ff38135cb9bfd4d92b0d7b5 Mon Sep 17 00:00:00 2001 From: Rasmus Fjord Date: Wed, 2 Nov 2016 20:55:15 +0100 Subject: [PATCH 27/88] Translation of Moving item(media / content) --- src/Umbraco.Web.UI.Client/src/views/content/move.html | 6 ++++-- src/Umbraco.Web.UI.Client/src/views/media/move.html | 6 ++++-- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 ++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 ++ src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 ++ 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/move.html b/src/Umbraco.Web.UI.Client/src/views/content/move.html index b64511ec22..6bf3ca817a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/move.html @@ -3,9 +3,11 @@

- Choose where to move {{currentNode.name}} to in the tree structure below + Choose where to move + {{currentNode.name}} + to in the tree structure below

- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/move.html b/src/Umbraco.Web.UI.Client/src/views/media/move.html index 95c30dfc40..3f71340ee3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/move.html @@ -3,8 +3,10 @@

- Choose where to move {{currentNode.name}} to in the tree structure below -

+ Choose where to move + {{currentNode.name}} + to in the tree structure below +

{{error.errorMsg}}

diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index ad05703939..c6d18c26f8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -31,6 +31,8 @@ Genudgiv hele sitet Gendan Sæt rettigheder for siden %0% + Hvor vil du flytte + hen til i træstrukturen? Rettigheder Fortryd ændringer Send til udgivelse diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index d1624d75d4..1c646d41db 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -29,6 +29,8 @@ Republish entire site Restore Set permissions for the page %0% + Choose where to move + to in the tree structure below Permissions Rollback Send To Publish diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 8d5348bc87..673c202117 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -28,6 +28,8 @@ Reload Republish entire site Set permissions for the page %0% + Choose where to move + to in the tree structure below Restore Permissions Rollback From cc5f127dcf8e520556ac4ed2e8ad3f400120a1e4 Mon Sep 17 00:00:00 2001 From: Rasmus Fjord Date: Wed, 2 Nov 2016 20:57:17 +0100 Subject: [PATCH 28/88] Translated "openInNewWindow" to a better sound in danish. --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index c6d18c26f8..9c0f3a80f8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -282,7 +282,7 @@ Link til side - Åbner det linket dokument i et nyt vindue eller fane + Åben linket i et nyt vindue eller fane Åbner det linket dokument i fuld visning af vinduet Åbner det linket dokument i "parent frame" From a83739c269d789f9daf6f7d2645ed72e7e2b2173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pjengaard=20Bank?= Date: Wed, 2 Nov 2016 20:58:21 +0100 Subject: [PATCH 29/88] Removed old options for linked targets and replaced with checkbox instead in linkpicker --- .../common/overlays/linkpicker/linkpicker.html | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html index aba5818e01..e063b7bab3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html @@ -18,18 +18,9 @@ - +
From ea67e217960a34f485a1aa9010d3c69544e0204e Mon Sep 17 00:00:00 2001 From: Thomas Nielsen Date: Wed, 2 Nov 2016 21:20:03 +0100 Subject: [PATCH 30/88] Link to mediatype from media properties --- .../Models/Mapping/MediaModelMapper.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index d13da67e1f..5f2a8bfd1d 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -155,7 +155,27 @@ namespace Umbraco.Web.Models.Mapping genericProperties.Add(link); } - TabsAndPropertiesResolver.MapGenericProperties(media, display, localizedText, genericProperties); + TabsAndPropertiesResolver.MapGenericProperties(media, display, localizedText, genericProperties, properties => { + if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null + && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var docTypeLink = string.Format("#/settings/mediatypes/edit/{0}", media.ContentTypeId); + + //Replace the doc type property + var docTypeProp = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProp.Value = new List + { + new + { + linkText = media.ContentType.Name, + url = docTypeLink, + target = "_self", icon = "icon-item-arrangement" + } + }; + docTypeProp.View = "urllist"; + } + + }); } } From 71f3948de2d52eb8af0661122faf538806a1eb01 Mon Sep 17 00:00:00 2001 From: Rasmus Fjord Date: Wed, 2 Nov 2016 21:23:02 +0100 Subject: [PATCH 31/88] Translated copy dialog (relate to and include descendants) to dk --- src/Umbraco.Web.UI.Client/src/views/content/copy.html | 7 ++++--- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/copy.html b/src/Umbraco.Web.UI.Client/src/views/content/copy.html index 3f2bfcdd3b..371e156513 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/copy.html @@ -51,13 +51,14 @@ - - + + + - + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 9c0f3a80f8..dee20dd3f6 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -279,6 +279,7 @@ Opret mappe... Relatér til original + Inkludér undersider Link til side diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 1c646d41db..54d54f14ec 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -291,6 +291,7 @@ Create folder... Relate to original + Include descendants The friendliest community Link to page diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 673c202117..58eed32205 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -293,6 +293,7 @@ Create folder... Relate to original + Include descendants The friendliest community Link to page From 018f9f17769263c04e82b952a076e7fde95c883e Mon Sep 17 00:00:00 2001 From: Rasmus Fjord Date: Wed, 2 Nov 2016 21:30:18 +0100 Subject: [PATCH 32/88] removed hg ignore --- .hgignore | 58 ------------------------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 .hgignore diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 13307d790b..0000000000 --- a/.hgignore +++ /dev/null @@ -1,58 +0,0 @@ -syntax: glob -*.obj -*.exe -*.pdb -*.user -*.aps -*.pch -*.vspscc -[Bb]in -[Db]ebug*/ -obj/ -[Rr]elease*/ -_ReSharper*/ -*.ncrunchsolution -*.ncrunchsolution.user -*.ncrunchproject -*.crunchsolution.cache -[Tt]est[Rr]esult* -[Bb]uild[Ll]og.* -*.[Pp]ublish.xml -*.suo -[sS]ource -[sS]andbox -umbraco.config -*.vs10x -App_Data\TEMP\* -umbraco\presentation\umbraco\plugins\* -umbraco\presentation\usercontrols\* -umbraco\presentation\scripts\* -umbraco\presentation\fonts\* -umbraco\presentation\css\* - -src\Umbraco.Web.UI\css\* -src\Umbraco.Web.UI\App_Code\* -src\Umbraco.Web.UI\App_Data\* -src\Umbraco.Tests\App_Data\* -src\Umbraco.Web.UI\media\* -src\Umbraco.Web.UI\masterpages\* -src\Umbraco.Web.UI\macroScripts\* -src\Umbraco.Web.UI\xslt\* -umbraco\presentation\umbraco\plugins\uComponents\uComponentsInstaller.ascx -umbraco\presentation\packages\uComponents\MultiNodePicker\CustomTreeService.asmx -_BuildOutput/* -*.ncrunchsolution -build/UmbracoCms.AllBinaries.zip -build/UmbracoCms.WebPI.zip -build/UmbracoCms.zip -build/*.nupkg -src/Umbraco.Tests/config/applications.config -src/Umbraco.Tests/config/trees.config -src/Umbraco.Web.UI/web.config -*.orig -src/Umbraco.Tests/config/404handlers.config -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/* From 7b345d6cb0243db4620af6eb66435288ea62e191 Mon Sep 17 00:00:00 2001 From: AronGreen Date: Wed, 2 Nov 2016 22:02:47 +0100 Subject: [PATCH 33/88] Danish translations related to Redirect URL Management --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index ffb0a9d96b..b0c7f9d639 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1146,4 +1146,21 @@ Mange hilsner fra Umbraco robotten ...eller indtast din egen validering Feltet er påkrævet + + Slå URL tracker fra + Slå URL tracker til + Original URL + Viderestillet til + Der er ikke lavet nogen viderestillinger + Når en udgivet side bliver omdøbt eller flyttet, vil en viderestilling automatisk blive lavet til den nye side. + Fjern + Er du sikker på at du vil fjerne viderestillingen fra '%0%' til '%1%'? + Viderestillings URL fjernet. + Fejl under fjernelse af viderestillings URL. + Er du sikker på at du vil slå URL trackeren fra? + URL tracker er nu slået fra. + Der opstod en fejl under forsøget på at slå URL trackeren fra, der findes mere information i logfilen. + URL tracker er nu slået fra. + Der opstod en fejl under forsøget på at slå URL trackeren til, der findes mere information i logfilen. + From 24402388b4b8b669987e65c2a1131e031dca77a2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 3 Nov 2016 09:57:10 +0100 Subject: [PATCH 34/88] position the overlay to the right --- .../src/views/components/upload/umb-file-dropzone.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html index 35d375e94d..6b05593e11 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html @@ -102,7 +102,7 @@ ng-if="mediatypepickerOverlay.show" model="mediatypepickerOverlay" view="mediatypepickerOverlay.view" - position="center"> + position="right"> From bf50c25854ea1cc66c92c629b707d0d4d572960e Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Thu, 3 Nov 2016 10:26:31 +0100 Subject: [PATCH 35/88] Only caching relation types First hackathon task done. :) --- src/Umbraco.Core/Persistence/RepositoryFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index a960d46eeb..ff0f9e9028 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -197,7 +197,7 @@ namespace Umbraco.Core.Persistence { return new RelationRepository( uow, - _cacheHelper, + _noCache, _logger, _sqlSyntax, CreateRelationTypeRepository(uow)); } @@ -343,4 +343,4 @@ namespace Umbraco.Core.Persistence _sqlSyntax); } } -} \ No newline at end of file +} From 85c2ec68ac641ed013ec12dd5b3d3079b8d0c7d9 Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 3 Nov 2016 10:39:59 +0100 Subject: [PATCH 36/88] allowing default image and file media types at root. --- .../Persistence/Migrations/Initial/BaseDataCreation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 9570024b09..8b33599436 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -149,8 +149,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial private void CreateCmsContentTypeData() { _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture" }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document" }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true }); _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user" }); } From 0441827578eb3d27dcba2cd585771645e0957799 Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 3 Nov 2016 10:41:06 +0100 Subject: [PATCH 37/88] using the provided media type when uploading images, instead of using default Image media type. --- src/Umbraco.Web/Editors/MediaController.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 08140f9c66..41551d099d 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -532,7 +532,16 @@ namespace Umbraco.Web.Editors var mediaType = Constants.Conventions.MediaTypes.File; if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) - mediaType = Constants.Conventions.MediaTypes.Image; + { + if (result.FormData.ContainsKey("contentTypeAlias")) + { + mediaType = result.FormData["contentTypeAlias"]; + } + else + { + mediaType = Constants.Conventions.MediaTypes.Image; + } + } //TODO: make the media item name "nice" since file names could be pretty ugly, we have // string extensions to do much of this but we'll need: From 22f37d02dc7efbaec066bedc04051fa40c3529e4 Mon Sep 17 00:00:00 2001 From: Andy Felton Date: Thu, 3 Nov 2016 16:00:04 +0000 Subject: [PATCH 38/88] Added missing colon --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f710e54215..58a8a26c2b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1359,8 +1359,8 @@ To manage your website, simply open the Umbraco back office and start adding con There was an error, check log for full error: %0%. Members - Total XML: %0%, Total: %1%, Total invalid: %2% - Media - Total XML: %0%, Total: %1%, Total invalid %2% - Content - Total XML: %0%, Total published: %1%, Total invalid %2% + Media - Total XML: %0%, Total: %1%, Total invalid: %2% + Content - Total XML: %0%, Total published: %1%, Total invalid: %2% Your site certificate was marked as valid. Certificate validation error: '%0%' From b198ecbc022976fe765fb050b6525e46f34586c9 Mon Sep 17 00:00:00 2001 From: Andy Felton Date: Thu, 3 Nov 2016 16:51:06 +0000 Subject: [PATCH 39/88] Modifed grammer as follows MacroErrors are set to 'throw' which will prevent some or all pages in your site from loading completely when there's any errors in macros. Rectifying this will set the value to 'inline'. modifed to "when there's any errors in macros" should be replaced with "if there are any errors in macros" --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f710e54215..fc8deca43d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1341,7 +1341,7 @@ To manage your website, simply open the Umbraco back office and start adding con Custom errors successfully set to '%0%'. MacroErrors are set to '%0%'. - MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely when there's any errors in macros. Rectifying this will set the value to '%1%'. + MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely if there are any errors in macros. Rectifying this will set the value to '%1%'. MacroErrors are now set to '%0%'. + Zatwierdź Typ Szukaj W górę @@ -356,8 +356,8 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Witaj... Szerokość Tak - Reorder - I am done reordering + Zmień kolejność + Kolejność została zmieniona Kolor tła @@ -786,7 +786,7 @@ Miłego dnia!]]> Administrator Pole kategorii - TRANSLATE ME: 'Change Your Password' + Zmień hasło! TRANSLATE ME: 'You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button' Zawartość Opis @@ -800,10 +800,10 @@ Miłego dnia!]]> Sekcje Wyłącz dostęp do Umbraco Hasło - TRANSLATE ME: 'Your password has been changed!' - TRANSLATE ME: 'Please confirm the new password' - TRANSLATE ME: 'Enter your new password' - TRANSLATE ME: 'Your new password cannot be blank!' + Twoje hasło zostało zmienione! + Proszę potwierdź nowe hasło! + Wprowadź nowe hasło + Nowe hasło nie może byc puste! TRANSLATE ME: 'There was a difference between the new password and the confirmed password. Please try again!' TRANSLATE ME: 'The confirmed password doesn't match the new password!' Zastąp prawa dostępu dla węzłów potomnych From 4f211aaf48daa789aac2d692e4550f4aa5a0b7d0 Mon Sep 17 00:00:00 2001 From: ricardo abreu Date: Thu, 3 Nov 2016 17:13:27 +0000 Subject: [PATCH 42/88] added finish button so page only reloads after user input. also added localization for en/en_us --- .../packager/views/install-local.controller.js | 16 ++++++++++++---- .../src/views/packager/views/install-local.html | 13 +++++++++++++ .../src/views/packager/views/repo.controller.js | 14 ++++++++++---- .../src/views/packager/views/repo.html | 10 ++++++++++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 1 + 6 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js index e5fba69a88..e34bc48ecd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js @@ -12,6 +12,7 @@ status: "", progress:0 }; + vm.installCompleted = false; vm.zipFile = { uploadStatus: "idle", uploadProgress: 0, @@ -137,10 +138,10 @@ localStorageService.set("packageInstallUri", "installed"); } - //reload on next digest (after cookie) - $timeout(function () { - $window.location.reload(true); - }); + vm.installState.status = localizationService.localize("packager_installStateCompleted"); + vm.installCompleted = true; + + }, installError); @@ -150,6 +151,13 @@ //This will return a rejection meaning that the promise change above will stop return $q.reject(); } + + vm.reloadPage = function() { + //reload on next digest (after cookie) + $timeout(function () { + $window.location.reload(true); + }); + } } angular.module("umbraco").controller("Umbraco.Editors.Packages.InstallLocalController", PackagesInstallLocalController); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html index 499e844588..6bb7c6fb14 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html @@ -159,6 +159,19 @@

{{vm.installState.status}}

+ + +
+ + +
+ diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js index e4afb661e3..fd9e7aaa02 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js @@ -30,6 +30,7 @@ vm.openLightbox = openLightbox; vm.closeLightbox = closeLightbox; vm.search = search; + vm.installCompleted = false; var currSort = "Latest"; //used to cancel any request in progress if another one needs to take it's place @@ -215,10 +216,8 @@ localStorageService.set("packageInstallUri", result.postInstallationPath); } - //reload on next digest (after cookie) - $timeout(function() { - window.location.reload(true); - }); + vm.installState.status = localizationService.localize("packager_installStateCompleted"); + vm.installCompleted = true; }, error); @@ -277,6 +276,13 @@ searchDebounced(); } + vm.reloadPage = function () { + //reload on next digest (after cookie) + $timeout(function () { + $window.location.reload(true); + }); + } + init(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html index 3975dc96d9..e7b14182ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html @@ -340,6 +340,16 @@

{{vm.installState.status}}

+
+ + +
+ diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f710e54215..50511bd61c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -825,7 +825,7 @@ To manage your website, simply open the Umbraco back office and start adding con Installing... Restarting, please wait... All done, your browser will now refresh, please wait... - + Please click finish to complete installation and reload page. Paste with full formatting (Not recommended) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 80c6faf07b..bb4016dded 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -825,6 +825,7 @@ To manage your website, simply open the Umbraco back office and start adding con Installing... Restarting, please wait... All done, your browser will now refresh, please wait... + Please click finish to complete installation and reload page. Paste with full formatting (Not recommended) From 7085f198a90eb18d63bdef9de671950ce14c6daa Mon Sep 17 00:00:00 2001 From: ricardo abreu Date: Thu, 3 Nov 2016 17:29:46 +0000 Subject: [PATCH 43/88] changed reloadPage function ($window to window) --- .../src/views/packager/views/repo.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js index fd9e7aaa02..5ae1d4bf2c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js @@ -279,7 +279,7 @@ vm.reloadPage = function () { //reload on next digest (after cookie) $timeout(function () { - $window.location.reload(true); + window.location.reload(true); }); } From a96f48d0a8b02e7558ebfdee938af3226ea2a59b Mon Sep 17 00:00:00 2001 From: Claus Date: Fri, 4 Nov 2016 13:52:36 +0100 Subject: [PATCH 44/88] changing media controller logic. --- src/Umbraco.Web/Editors/MediaController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 41551d099d..932b3f6766 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -531,17 +531,17 @@ namespace Umbraco.Web.Editors { var mediaType = Constants.Conventions.MediaTypes.File; - if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) + if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.Image) { - if (result.FormData.ContainsKey("contentTypeAlias")) - { - mediaType = result.FormData["contentTypeAlias"]; - } - else + if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) { mediaType = Constants.Conventions.MediaTypes.Image; } } + else + { + mediaType = result.FormData["contentTypeAlias"]; + } //TODO: make the media item name "nice" since file names could be pretty ugly, we have // string extensions to do much of this but we'll need: From 8944870d4ff9a14bf02fdf0d2f7079b100b0b523 Mon Sep 17 00:00:00 2001 From: Ivar Date: Mon, 24 Oct 2016 22:41:44 +0000 Subject: [PATCH 45/88] Renamed icon-globe---europe-africa to icon-globe-europe-africa --- src/Umbraco.Web.UI.Client/src/less/helveticons.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/helveticons.less b/src/Umbraco.Web.UI.Client/src/less/helveticons.less index a3c2072d86..0e4695d761 100644 --- a/src/Umbraco.Web.UI.Client/src/less/helveticons.less +++ b/src/Umbraco.Web.UI.Client/src/less/helveticons.less @@ -1569,7 +1569,7 @@ i.small{ .icon-hd:before { content: "\e1f9"; } -.icon-globe-europe---africa:before { +.icon-globe-europe-africa:before { content: "\e1fa"; } .icon-hat:before { From fcca95787b30d88d6733f22faa7e848e055df331 Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 7 Nov 2016 13:03:43 +0100 Subject: [PATCH 46/88] fixing backwards compatibility. --- src/Umbraco.Web.UI.Client/src/less/helveticons.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/helveticons.less b/src/Umbraco.Web.UI.Client/src/less/helveticons.less index 0e4695d761..9a317e09fb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/helveticons.less +++ b/src/Umbraco.Web.UI.Client/src/less/helveticons.less @@ -1569,7 +1569,8 @@ i.small{ .icon-hd:before { content: "\e1f9"; } -.icon-globe-europe-africa:before { +.icon-globe-europe-africa:before, +.icon-globe-europe---africa:before { content: "\e1fa"; } .icon-hat:before { From e429f8611ec4e837276ca668770f47c0f3c70e04 Mon Sep 17 00:00:00 2001 From: crgrieve Date: Mon, 7 Nov 2016 20:53:59 +0000 Subject: [PATCH 47/88] Remove all whitespace when parsing MNTP allowed doctypes. --- .../src/views/common/dialogs/treepicker.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js index 2d8d38cf50..740791abf9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js @@ -259,7 +259,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", } }); } else { - var a = dialogOptions.filter.toLowerCase().split(','); + var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); angular.forEach(nodes, function (value, key) { var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; From c0a2326a8d2ccce554f16247fc740c3c55da6443 Mon Sep 17 00:00:00 2001 From: crgrieve Date: Mon, 7 Nov 2016 21:03:57 +0000 Subject: [PATCH 48/88] Removing whitespace when parsing MNTP comma separated list. --- .../src/views/common/dialogs/treepicker.controller.js | 2 +- .../views/common/overlays/treepicker/treepicker.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js index 740791abf9..2d8d38cf50 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js @@ -259,7 +259,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", } }); } else { - var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); + var a = dialogOptions.filter.toLowerCase().split(','); angular.forEach(nodes, function (value, key) { var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index e74bfd20a4..8e87a2daff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -326,7 +326,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", } }); } else { - var a = dialogOptions.filter.toLowerCase().split(','); + var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); angular.forEach(nodes, function (value, key) { var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; From 8bb069e996a452b2dd4fa016bca32c3ec6d933cc Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 8 Nov 2016 09:55:24 +0100 Subject: [PATCH 49/88] U4-9134 XSS security issue in the grid exposing xss clean method on templateutilities. making the clean xss string extensions public instead of internal. ensuring the included grid renderers clean for xss. ensuring the included grid editors using html.raw with value directly, cleans for xss. --- src/Umbraco.Core/StringExtensions.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 + .../Partials/Grid/Bootstrap2-Fluid.cshtml | 31 ++-- .../Views/Partials/Grid/Bootstrap2.cshtml | 31 ++-- .../Partials/Grid/Bootstrap3-Fluid.cshtml | 32 ++-- .../Views/Partials/Grid/Bootstrap3.cshtml | 31 ++-- .../Views/Partials/Grid/Editors/Base.cshtml | 1 - .../Views/Partials/Grid/Editors/Embed.cshtml | 1 - .../Views/Partials/Grid/Editors/Macro.cshtml | 2 - .../Views/Partials/Grid/Editors/Media.cshtml | 1 - .../Partials/Grid/Editors/TextString.cshtml | 5 +- .../Templates/TemplateUtilities.cs | 143 +++++++++--------- 12 files changed, 164 insertions(+), 118 deletions(-) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 036b5b979f..27de06c371 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -184,7 +184,7 @@ namespace Umbraco.Core /// /// /// - internal static string CleanForXss(this string input, params char[] ignoreFromClean) + public static string CleanForXss(this string input, params char[] ignoreFromClean) { //remove any html input = input.StripHtml(); diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 6f2728a241..512c8177a9 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1973,6 +1973,8 @@ + + Web.Template.config diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml index 446a82f510..65b9b8abc7 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml @@ -64,21 +64,32 @@ JObject cfg = contentItem.config; if(cfg != null) - foreach (JProperty property in cfg.Properties()) { - attrs.Add(property.Name + "='" + property.Value.ToString() + "'"); + foreach (JProperty property in cfg.Properties()) + { + var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + if (string.IsNullOrWhiteSpace(propertyValue) == false) + { + attrs.Add(property.Name + "='" + propertyValue + "'"); + } } - + JObject style = contentItem.styles; - if (style != null) { - var cssVals = new List(); - foreach (JProperty property in style.Properties()) - cssVals.Add(property.Name + ":" + property.Value.ToString() + ";"); + if (style != null) { + var cssVals = new List(); + foreach (JProperty property in style.Properties()) + { + var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + if (string.IsNullOrWhiteSpace(propertyValue) == false) + { + cssVals.Add(property.Name + ":" + propertyValue + ";"); + } + } - if (cssVals.Any()) - attrs.Add("style='" + string.Join(" ", cssVals) + "'"); + if (cssVals.Any()) + attrs.Add("style='" + string.Join(" ", cssVals) + "'"); } - + return new MvcHtmlString(string.Join(" ", attrs)); } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml index 6bc730e1f8..37e3c84dad 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml @@ -64,21 +64,32 @@ JObject cfg = contentItem.config; if(cfg != null) - foreach (JProperty property in cfg.Properties()) { - attrs.Add(property.Name + "=\"" + property.Value.ToString() + "\""); + foreach (JProperty property in cfg.Properties()) + { + var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + if (string.IsNullOrWhiteSpace(propertyValue) == false) + { + attrs.Add(property.Name + "=\"" + propertyValue + "\""); + } } - + JObject style = contentItem.styles; - if (style != null) { - var cssVals = new List(); - foreach (JProperty property in style.Properties()) - cssVals.Add(property.Name + ":" + property.Value.ToString() + ";"); + if (style != null) { + var cssVals = new List(); + foreach (JProperty property in style.Properties()) + { + var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + if (string.IsNullOrWhiteSpace(propertyValue) == false) + { + cssVals.Add(property.Name + ":" + propertyValue + ";"); + } + } - if (cssVals.Any()) - attrs.Add("style=\"" + string.Join(" ", cssVals) + "\""); + if (cssVals.Any()) + attrs.Add("style=\"" + string.Join(" ", cssVals) + "\""); } - + return new MvcHtmlString(string.Join(" ", attrs)); } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml index 1244821d7e..45be239245 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml @@ -5,6 +5,7 @@ @* Razor helpers located at the bottom of this file *@ + @if (Model != null && Model.sections != null) { var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1; @@ -59,21 +60,32 @@ JObject cfg = contentItem.config; if(cfg != null) - foreach (JProperty property in cfg.Properties()) { - attrs.Add(property.Name + "='" + property.Value.ToString() + "'"); + foreach (JProperty property in cfg.Properties()) + { + var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + if (string.IsNullOrWhiteSpace(propertyValue) == false) + { + attrs.Add(property.Name + "='" + propertyValue + "'"); + } } - + JObject style = contentItem.styles; - if (style != null) { - var cssVals = new List(); - foreach (JProperty property in style.Properties()) - cssVals.Add(property.Name + ":" + property.Value.ToString() + ";"); + if (style != null) { + var cssVals = new List(); + foreach (JProperty property in style.Properties()) + { + var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + if (string.IsNullOrWhiteSpace(propertyValue) == false) + { + cssVals.Add(property.Name + ":" + propertyValue + ";"); + } + } - if (cssVals.Any()) - attrs.Add("style='" + string.Join(" ", cssVals) + "'"); + if (cssVals.Any()) + attrs.Add("style='" + string.Join(" ", cssVals) + "'"); } - + return new MvcHtmlString(string.Join(" ", attrs)); } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml index f76028d296..afadd3d93e 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml @@ -64,21 +64,32 @@ JObject cfg = contentItem.config; if(cfg != null) - foreach (JProperty property in cfg.Properties()) { - attrs.Add(property.Name + "=\"" + property.Value.ToString() + "\""); + foreach (JProperty property in cfg.Properties()) + { + var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + if (string.IsNullOrWhiteSpace(propertyValue) == false) + { + attrs.Add(property.Name + "=\"" + propertyValue +"\""); + } } - + JObject style = contentItem.styles; - if (style != null) { - var cssVals = new List(); - foreach (JProperty property in style.Properties()) - cssVals.Add(property.Name + ":" + property.Value.ToString() + ";"); + if (style != null) { + var cssVals = new List(); + foreach (JProperty property in style.Properties()) + { + var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + if (string.IsNullOrWhiteSpace(propertyValue) == false) + { + cssVals.Add(property.Name + ":" + propertyValue + ";"); + } + } - if (cssVals.Any()) - attrs.Add("style=\"" + string.Join(" ", cssVals) + "\""); + if (cssVals.Any()) + attrs.Add("style=\"" + string.Join(" ", cssVals) + "\""); } - + return new MvcHtmlString(string.Join(" ", attrs)); } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Base.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Base.cshtml index a86c04819a..ffb7603048 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Base.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Base.cshtml @@ -1,5 +1,4 @@ @model dynamic -@using Umbraco.Web.Templates @functions { public static string EditorView(dynamic contentItem) diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml index 4fd66ddb90..c27be6bcdf 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml @@ -1,3 +1,2 @@ @model dynamic -@using Umbraco.Web.Templates @Html.Raw(Model.value) diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Macro.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Macro.cshtml index e0822808d8..ed08bb2484 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Macro.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Macro.cshtml @@ -1,6 +1,4 @@ @inherits UmbracoViewPage -@using Umbraco.Web.Templates - @if (Model.value != null) { diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml index f5dfc6459c..5b5adbdc7d 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml @@ -1,5 +1,4 @@ @model dynamic -@using Umbraco.Web.Templates @if (Model.value != null) { diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml index a031c658a9..4a15201997 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml @@ -4,10 +4,9 @@ @if (Model.editor.config.markup != null) { string markup = Model.editor.config.markup.ToString(); - var UmbracoHelper = new UmbracoHelper(UmbracoContext.Current); - markup = markup.Replace("#value#", UmbracoHelper.ReplaceLineBreaksForHtml(Model.value.ToString())); + markup = markup.Replace("#value#", UmbracoHelper.ReplaceLineBreaksForHtml(TemplateUtilities.CleanForXss(Model.value.ToString()))); markup = markup.Replace("#style#", Model.editor.config.style.ToString()); @@ -17,6 +16,6 @@ else { -
@Model.value
+
@TemplateUtilities.CleanForXss(Model.value.ToString())
} diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 881cb563c5..c56d7b5b8a 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -7,17 +7,17 @@ using Umbraco.Core.Logging; namespace Umbraco.Web.Templates { - //NOTE: I realize there is only one class in this namespace but I'm pretty positive that there will be more classes in - //this namespace once we start migrating and cleaning up more code. + //NOTE: I realize there is only one class in this namespace but I'm pretty positive that there will be more classes in + //this namespace once we start migrating and cleaning up more code. - /// - /// Utility class used for templates - /// - public static class TemplateUtilities - { + /// + /// Utility class used for templates + /// + public static class TemplateUtilities + { //TODO: Pass in an Umbraco context!!!!!!!! Don't rely on the singleton so things are more testable internal static string ParseInternalLinks(string text, bool preview) - { + { // save and set for url provider var inPreviewMode = UmbracoContext.Current.InPreviewMode; UmbracoContext.Current.InPreviewMode = preview; @@ -33,79 +33,84 @@ namespace Umbraco.Web.Templates } return text; - } + } - /// - /// Parses the string looking for the {localLink} syntax and updates them to their correct links. - /// - /// - /// - public static string ParseInternalLinks(string text) - { + /// + /// Parses the string looking for the {localLink} syntax and updates them to their correct links. + /// + /// + /// + public static string ParseInternalLinks(string text) + { //TODO: Pass in an Umbraco context!!!!!!!! Don't rely on the singleton so things are more testable, better yet, pass in urlprovider, routing context, separately - //don't attempt to proceed without a context as we cannot lookup urls without one - if (UmbracoContext.Current == null || UmbracoContext.Current.RoutingContext == null) - { - return text; - } + //don't attempt to proceed without a context as we cannot lookup urls without one + if (UmbracoContext.Current == null || UmbracoContext.Current.RoutingContext == null) + { + return text; + } - var urlProvider = UmbracoContext.Current.UrlProvider; + var urlProvider = UmbracoContext.Current.UrlProvider; - // Parse internal links - var tags = Regex.Matches(text, @"href=""[/]?(?:\{|\%7B)localLink:([0-9]+)(?:\}|\%7D)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - foreach (Match tag in tags) - if (tag.Groups.Count > 0) - { - var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); - var newLink = urlProvider.GetUrl(int.Parse(id)); - text = text.Replace(tag.Value, "href=\"" + newLink); - } + // Parse internal links + var tags = Regex.Matches(text, @"href=""[/]?(?:\{|\%7B)localLink:([0-9]+)(?:\}|\%7D)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + foreach (Match tag in tags) + if (tag.Groups.Count > 0) + { + var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); + var newLink = urlProvider.GetUrl(int.Parse(id)); + text = text.Replace(tag.Value, "href=\"" + newLink); + } return text; - } + } - // static compiled regex for faster performance - private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + // static compiled regex for faster performance + private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - /// - /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. - /// - /// - /// - /// - /// When used with a Virtual-Directory set-up, this would resolve all URLs correctly. - /// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs. - /// - public static string ResolveUrlsFromTextString(string text) - { + /// + /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. + /// + /// + /// + /// + /// When used with a Virtual-Directory set-up, this would resolve all URLs correctly. + /// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs. + /// + public static string ResolveUrlsFromTextString(string text) + { if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString == false) return text; - using (var timer = DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) - { - // find all relative urls (ie. urls that contain ~) - var tags = ResolveUrlPattern.Matches(text); - LogHelper.Debug(typeof(IOHelper), "After regex: " + timer.Stopwatch.ElapsedMilliseconds + " matched: " + tags.Count); - foreach (Match tag in tags) - { - var url = ""; - if (tag.Groups[1].Success) - url = tag.Groups[1].Value; + using (var timer = DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) + { + // find all relative urls (ie. urls that contain ~) + var tags = ResolveUrlPattern.Matches(text); + LogHelper.Debug(typeof(IOHelper), "After regex: " + timer.Stopwatch.ElapsedMilliseconds + " matched: " + tags.Count); + foreach (Match tag in tags) + { + var url = ""; + if (tag.Groups[1].Success) + url = tag.Groups[1].Value; - // The richtext editor inserts a slash in front of the url. That's why we need this little fix - // if (url.StartsWith("/")) - // text = text.Replace(url, ResolveUrl(url.Substring(1))); - // else - if (String.IsNullOrEmpty(url) == false) - { - var resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : IOHelper.ResolveUrl(url); - text = text.Replace(url, resolvedUrl); - } - } - } + // The richtext editor inserts a slash in front of the url. That's why we need this little fix + // if (url.StartsWith("/")) + // text = text.Replace(url, ResolveUrl(url.Substring(1))); + // else + if (String.IsNullOrEmpty(url) == false) + { + var resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : IOHelper.ResolveUrl(url); + text = text.Replace(url, resolvedUrl); + } + } + } - return text; - } + return text; + } - } + public static string CleanForXss(string text, params char[] ignoreFromClean) + { + return text.CleanForXss(ignoreFromClean); + } + } } From 957096a70a6930955361654ed9a9b562852f1269 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 8 Nov 2016 10:33:05 +0100 Subject: [PATCH 50/88] fixing links to be https. --- .../default/StartupDashboardIntro.html | 166 +++++++++--------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardIntro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardIntro.html index 73a6399b67..3782898312 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardIntro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/default/StartupDashboardIntro.html @@ -1,44 +1,44 @@ -
- - - -
- -
-
- - -
-
- - -
-
- - -
-
-
-
-
- -
-
- -
-
-
- - -
-
- - - - -
-

Welcome to The Friendly CMS

+
+ + + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+ +
+
+
+ + +
+
+ + + + +
+

Welcome to The Friendly CMS

Thank you for choosing Umbraco - we think this could be the beginning of something beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast as possible.

@@ -50,46 +50,46 @@
  • Watch our tutorial videos (some are free, some require a subscription)
  • Find out about our productivity boosting tools and commercial support
  • Find out about real-life training and certification opportunities
  • - - -
    -
    - - Umbraco.TV - Hours of Umbraco Video Tutorials - - - - -

    Umbraco.TV - Learn from the source!

    -
    - -

    - Umbraco.TV will help you go from zero to Umbraco - hero at a pace that suits you. Our easy to follow - online training videos will give you the fundamental - knowledge to start building awesome Umbraco websites. -

    -
    - -
    - - - Our Umbraco - - - -

    Our Umbraco - The Friendliest Community

    -
    - -

    - Our Umbraco - the official community site is your one - stop for everything Umbraco. Whether you need a - question answered or looking for cool plugins, the - worlds best community is just a click away. -

    - -
    -
    -
    - + + +
    +
    + + Umbraco.TV - Hours of Umbraco Video Tutorials + + + + +

    Umbraco.TV - Learn from the source!

    +
    + +

    + Umbraco.TV will help you go from zero to Umbraco + hero at a pace that suits you. Our easy to follow + online training videos will give you the fundamental + knowledge to start building awesome Umbraco websites. +

    +
    + +
    + + + Our Umbraco + + + +

    Our Umbraco - The Friendliest Community

    +
    + +

    + Our Umbraco - the official community site is your one + stop for everything Umbraco. Whether you need a + question answered or looking for cool plugins, the + worlds best community is just a click away. +

    + +
    +
    +
    +
    \ No newline at end of file From 4a42bfa9bbdfbe0be124f8ec179ca764a0d5bbf3 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 8 Nov 2016 10:53:29 +0100 Subject: [PATCH 51/88] U4-9160 Headline in the "Republish entire site" dialog is not translated --- .../umbraco_client/Application/UmbracoApplicationActions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoApplicationActions.js b/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoApplicationActions.js index 46b7a15e48..4c9017e159 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoApplicationActions.js +++ b/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoApplicationActions.js @@ -281,7 +281,7 @@ Umbraco.Application.Actions = function() { actionRePublish: function() { /// - UmbClientMgr.openModalWindow('dialogs/republish.aspx?rnd=' + this._utils.generateRandom(), 'Republishing entire site', true, 450, 210); + UmbClientMgr.openModalWindow('dialogs/republish.aspx?rnd=' + this._utils.generateRandom(), uiKeys['actions_republish'], true, 450, 210); }, actionAssignDomain: function() { From 16ae5cf6345122395d944e91fd3937027198fcc0 Mon Sep 17 00:00:00 2001 From: Thomas Nielsen Date: Tue, 8 Nov 2016 23:00:14 +0100 Subject: [PATCH 52/88] Link to member type from member properties --- .../Models/Mapping/MemberModelMapper.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 050fe3c726..6d4bdd7941 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -233,7 +233,26 @@ namespace Umbraco.Web.Models.Mapping }; - TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties); + TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties, properties => { + if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null + && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var docTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); + + //Replace the doc type property + var docTypeProp = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProp.Value = new List + { + new + { + linkText = member.ContentType.Name, + url = docTypeLink, + target = "_self", icon = "icon-item-arrangement" + } + }; + docTypeProp.View = "urllist"; + } + }); //check if there's an approval field var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider; From 981c0b83d3ea2784173f4273a57dee2457f75cfb Mon Sep 17 00:00:00 2001 From: Thomas Nielsen Date: Tue, 8 Nov 2016 23:16:10 +0100 Subject: [PATCH 53/88] Oops wrong pull request --- .../Models/Mapping/MemberModelMapper.cs | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 6d4bdd7941..050fe3c726 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -233,26 +233,7 @@ namespace Umbraco.Web.Models.Mapping }; - TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties, properties => { - if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null - && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) - { - var docTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); - - //Replace the doc type property - var docTypeProp = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - docTypeProp.Value = new List - { - new - { - linkText = member.ContentType.Name, - url = docTypeLink, - target = "_self", icon = "icon-item-arrangement" - } - }; - docTypeProp.View = "urllist"; - } - }); + TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties); //check if there's an approval field var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider; From cbfd04e95006308d70905a0e3c6007c0373535d6 Mon Sep 17 00:00:00 2001 From: Thomas Nielsen Date: Tue, 8 Nov 2016 23:37:29 +0100 Subject: [PATCH 54/88] Link to member type from member properties --- .../Models/Mapping/MemberModelMapper.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 050fe3c726..6d4bdd7941 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -233,7 +233,26 @@ namespace Umbraco.Web.Models.Mapping }; - TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties); + TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties, properties => { + if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null + && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var docTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); + + //Replace the doc type property + var docTypeProp = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProp.Value = new List + { + new + { + linkText = member.ContentType.Name, + url = docTypeLink, + target = "_self", icon = "icon-item-arrangement" + } + }; + docTypeProp.View = "urllist"; + } + }); //check if there's an approval field var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider; From 7018969d14186c2f643ee3b2241f933f8b383a8c Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 9 Nov 2016 09:29:43 +0100 Subject: [PATCH 55/88] removing unused language keys. --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 11 ---- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 12 ---- .../umbraco/config/lang/en_us.xml | 12 ---- src/Umbraco.Web.UI/umbraco/config/lang/fr.xml | 58 ++++++++----------- src/Umbraco.Web.UI/umbraco/config/lang/ja.xml | 14 +---- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 12 ---- 6 files changed, 24 insertions(+), 95 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index b6dac0afc9..b0452fb631 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -277,18 +277,11 @@ Vælg Se cache element Opret mappe... - Relatér til original Inkludér undersider - Link til side - Åben linket i et nyt vindue eller fane - Åbner det linket dokument i fuld visning af vinduet - Åbner det linket dokument i "parent frame" - Link til medie - Vælg medie Vælg ikon Vælg item @@ -297,14 +290,10 @@ Vælg indhold Vælg medlem Vælg medlemsgruppe - Der er ingen parametre for denne makro - Link dit Fjern link fra dit - konto - Vælg editor diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 545c32480f..bdc2647563 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -289,19 +289,12 @@ Pick item View Cache Item Create folder... - Relate to original Include descendants The friendliest community - Link to page - Opens the linked document in a new window or tab - Opens the linked document in the full body of the window - Opens the linked document in the parent frame - Link to media - Select media Select icon Select item @@ -311,19 +304,14 @@ Select member Select member group No icons were found - There are no parameters for this macro - External login providers Exception Details Stacktrace Inner Exception - Link your Un-Link your - account - Select editor diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index c0ef0b8c37..7544cacc0c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -291,19 +291,12 @@ Pick item View Cache Item Create folder... - Relate to original Include descendants The friendliest community - Link to page - Opens the linked document in a new window or tab - Opens the linked document in the full body of the window - Opens the linked document in the parent frame - Link to media - Select media Select icon Select item @@ -312,19 +305,14 @@ Select content Select member Select member group - There are no parameters for this macro - External login providers Exception Details Stacktrace Inner Exception - Link your Un-Link your - account - Select editor diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index 40e18ce298..39cafeef7f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -287,41 +287,29 @@ Cliquez sur l'image pour la voir en taille réelle Sélectionner un élément Voir l'élément de cache - Créer un répertoire... - - Lier à l'original - La communauté la plus amicale - - Lier à la page - - Ouvre le document lié dans une nouvelle fenêtre ou un nouvel onglet - Ouvre le document lié dans l'entièreté de la fenêtre - Ouvre le document lié dans le conteneur parent - - Lier à un media - - Sélectionner le media - Sélectionner l'icône - Sélectionner l'élément - Sélectionner le lien - Sélectionner la macro - Sélectionner le contenu - Sélectionner le membre - Sélectionner le groupe de membres - - Il n'y a pas de paramètres pour cette macro - - Fournisseurs externes d'identification - Détails de l'exception - Trace d'exécution - Exception interne - - Liez votre - Enlevez votre - - compte - - Sélectionner un éditeur + Créer un répertoire... + Lier à l'original + La communauté la plus amicale + Lier à la page + Ouvre le document lié dans une nouvelle fenêtre ou un nouvel onglet + Lier à un media + Sélectionner le media + Sélectionner l'icône + Sélectionner l'élément + Sélectionner le lien + Sélectionner la macro + Sélectionner le contenu + Sélectionner le membre + Sélectionner le groupe de membres + Il n'y a pas de paramètres pour cette macro + Fournisseurs externes d'identification + Détails de l'exception + Trace d'exécution + Exception interne + Liez votre + Enlevez votre + compte + Sélectionner un éditeur クリックすると画像がフルサイズで表示されます 項目の選択 キャッシュされている項目の表示 - フォルダーの作成... - + フォルダーの作成... オリジナルに関連付ける フレンドリーなコミュニティ - ページへリンク - リンク ドキュメントを新しいウィンドウまたはタブで開く - リンク ドキュメントをウィンドウ全文表示で開く - 親フレームでリンク ドキュメントを開く - メディアへリンク - メディアの選択 アイコンの選択 アイテムの選択 @@ -296,19 +289,14 @@ コンテンツの選択 メンバーの選択 メンバー グループの選択 - このマクロのパラメーターはありません - 外部ログイン プロバイダー 例外の詳細 スタックトレース Inner Exception - 次をリンク: 次をリンク解除: - アカウント - エディターの選択 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index d71b4423aa..255bf62d5d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -340,18 +340,11 @@ Выберите элемент Просмотр элемента кэша Создать папку... - Связать с оригиналом Самое дружелюбное сообщество - Ссылка на страницу - Открывает документ по ссылке в новом окне или вкладке браузера - Открывает документ по ссылке в полноэкранном режиме - Открывает документ по ссылке в родительском фрейме - Ссылка на медиа-файл - Выбрать медиа Выбрать значок Выбрать элемент @@ -360,19 +353,14 @@ Выбрать содержимое Выбрать участника Выбрать группу участников - Это макрос без параметров - Провайдеры аутентификации Подробное сообщение об ошибке Трассировка стека Внутренняя ошибка - Связать Разорвать связь - учетную запись - Выбрать редактор From ff2805c428b4ef7318297fb7dd8f0331cb54a7d9 Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 9 Nov 2016 11:13:35 +0100 Subject: [PATCH 56/88] mostly just formatting and cleanup - no changes as such. --- .../Models/Mapping/ContentModelMapper.cs | 132 +++++--------- .../Models/Mapping/MediaModelMapper.cs | 96 ++++------- .../Models/Mapping/MemberModelMapper.cs | 161 +++++++----------- 3 files changed, 144 insertions(+), 245 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index b7a6ac1f4a..e179159e7c 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -6,7 +6,6 @@ using System.Web; using System.Web.Mvc; using System.Web.Routing; using AutoMapper; -using umbraco; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Mapping; @@ -29,38 +28,18 @@ namespace Umbraco.Web.Models.Mapping //FROM IContent TO ContentItemDisplay config.CreateMap() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember( - dto => dto.Updater, - expression => expression.ResolveUsing(new CreatorResolver())) - .ForMember( - dto => dto.Icon, - expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember( - dto => dto.ContentTypeAlias, - expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember( - dto => dto.ContentTypeName, - expression => expression.MapFrom(content => content.ContentType.Name)) - .ForMember( - dto => dto.IsContainer, - expression => expression.MapFrom(content => content.ContentType.IsContainer)) - .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) - .ForMember( - dto => dto.Trashed, - expression => expression.MapFrom(content => content.Trashed)) - .ForMember( - dto => dto.PublishDate, - expression => expression.MapFrom(content => GetPublishedDate(content, applicationContext))) - .ForMember( - dto => dto.TemplateAlias, expression => expression.MapFrom(content => content.Template.Alias)) - .ForMember( - dto => dto.HasPublishedVersion, - expression => expression.MapFrom(content => content.HasPublishedVersion)) - .ForMember( - dto => dto.Urls, + .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) + .ForMember(display => display.Updater, expression => expression.ResolveUsing(new CreatorResolver())) + .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) + .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) + .ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) + .ForMember(display => display.IsContainer, expression => expression.MapFrom(content => content.ContentType.IsContainer)) + .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) + .ForMember(display => display.Trashed, expression => expression.MapFrom(content => content.Trashed)) + .ForMember(display => display.PublishDate, expression => expression.MapFrom(content => GetPublishedDate(content, applicationContext))) + .ForMember(display => display.TemplateAlias, expression => expression.MapFrom(content => content.Template.Alias)) + .ForMember(display => display.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion)) + .ForMember(display => display.Urls, expression => expression.MapFrom(content => UmbracoContext.Current == null ? new[] {"Cannot generate urls without a current Umbraco Context"} @@ -74,47 +53,28 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) .ForMember(display => display.AllowedActions, expression => expression.ResolveUsing( new ActionButtonsResolver(new Lazy(() => applicationContext.Services.UserService)))) - .AfterMap((media, display) => AfterMap(media, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, + .AfterMap((content, display) => AfterMap(content, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, applicationContext.Services.ContentTypeService)); //FROM IContent TO ContentItemBasic config.CreateMap>() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember( - dto => dto.Updater, - expression => expression.ResolveUsing(new CreatorResolver())) - .ForMember( - dto => dto.Icon, - expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember( - dto => dto.Trashed, - expression => expression.MapFrom(content => content.Trashed)) - .ForMember( - dto => dto.HasPublishedVersion, - expression => expression.MapFrom(content => content.HasPublishedVersion)) - .ForMember( - dto => dto.ContentTypeAlias, - expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember(display => display.Alias, expression => expression.Ignore()); + .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) + .ForMember(dto => dto.Updater, expression => expression.ResolveUsing(new CreatorResolver())) + .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) + .ForMember(dto => dto.Trashed, expression => expression.MapFrom(content => content.Trashed)) + .ForMember(dto => dto.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion)) + .ForMember(dto => dto.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) + .ForMember(dto => dto.Alias, expression => expression.Ignore()); //FROM IContent TO ContentItemDto config.CreateMap>() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember( - dto => dto.HasPublishedVersion, - expression => expression.MapFrom(content => content.HasPublishedVersion)) - .ForMember(display => display.Updater, expression => expression.Ignore()) - .ForMember(display => display.Icon, expression => expression.Ignore()) - .ForMember(display => display.Alias, expression => expression.Ignore()); - - + .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) + .ForMember(dto => dto.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion)) + .ForMember(dto => dto.Updater, expression => expression.Ignore()) + .ForMember(dto => dto.Icon, expression => expression.Ignore()) + .ForMember(dto => dto.Alias, expression => expression.Ignore()); } - /// /// Maps the generic tab with custom properties for content /// @@ -123,7 +83,7 @@ namespace Umbraco.Web.Models.Mapping /// /// /// - private static void AfterMap(IContent content, ContentItemDisplay display, IDataTypeService dataTypeService, + private static void AfterMap(IContent content, ContentItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, IContentTypeService contentTypeService) { //map the IsChildOfListView (this is actually if it is a descendant of a list view!) @@ -151,7 +111,6 @@ namespace Umbraco.Web.Models.Mapping display.IsChildOfListView = ancesctorListView != null; } } - //map the tree node url if (HttpContext.Current != null) @@ -160,9 +119,9 @@ namespace Umbraco.Web.Models.Mapping var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null)); display.TreeNodeUrl = url; } - + //fill in the template config to be passed to the template drop down. - var templateItemConfig = new Dictionary { { "", "Choose..." } }; + var templateItemConfig = new Dictionary {{"", "Choose..."}}; foreach (var t in content.ContentType.AllowedTemplates .Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false)) { @@ -173,7 +132,7 @@ namespace Umbraco.Web.Models.Mapping { TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText); } - + var properties = new List { new ContentPropertyDisplay @@ -183,26 +142,26 @@ namespace Umbraco.Web.Models.Mapping Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), View = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View }, - new ContentPropertyDisplay + new ContentPropertyDisplay { Alias = string.Format("{0}releasedate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = localizedText.Localize("content/releaseDate"), Value = display.ReleaseDate.HasValue ? display.ReleaseDate.Value.ToIsoString() : null, //Not editible for people without publish permission (U4-287) - View = display.AllowedActions.Contains(ActionPublish.Instance.Letter) ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View, + View = display.AllowedActions.Contains(ActionPublish.Instance.Letter) ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View, Config = new Dictionary { {"offsetTime", "1"} } //TODO: Fix up hard coded datepicker - } , + }, new ContentPropertyDisplay { Alias = string.Format("{0}expiredate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = localizedText.Localize("content/unpublishDate"), Value = display.ExpireDate.HasValue ? display.ExpireDate.Value.ToIsoString() : null, //Not editible for people without publish permission (U4-287) - View = display.AllowedActions.Contains(ActionPublish.Instance.Letter) ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View, + View = display.AllowedActions.Contains(ActionPublish.Instance.Letter) ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View, Config = new Dictionary { {"offsetTime", "1"} @@ -246,21 +205,21 @@ namespace Umbraco.Web.Models.Mapping var docTypeLink = string.Format("#/settings/documenttypes/edit/{0}", currentDocumentTypeId); //Replace the doc type property - var docTypeProp = genericProperties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - docTypeProp.Value = new List + var docTypeProperty = genericProperties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProperty.Value = new List { new { linkText = currentDocumentTypeName, url = docTypeLink, - target = "_self", icon = "icon-item-arrangement" + target = "_self", + icon = "icon-item-arrangement" } }; //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor - docTypeProp.View = "urllist"; + docTypeProperty.View = "urllist"; } }); - } /// @@ -305,13 +264,13 @@ namespace Umbraco.Web.Models.Mapping var svc = _userService.Value; var permissions = svc.GetPermissions( - //TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is - // with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null - // refrence exception :( - UmbracoContext.Current.Security.CurrentUser, - // Here we need to do a special check since this could be new content, in which case we need to get the permissions - // from the parent, not the existing one otherwise permissions would be coming from the root since Id is 0. - source.HasIdentity ? source.Id : source.ParentId) + //TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is + // with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null + // refrence exception :( + UmbracoContext.Current.Security.CurrentUser, + // Here we need to do a special check since this could be new content, in which case we need to get the permissions + // from the parent, not the existing one otherwise permissions would be coming from the root since Id is 0. + source.HasIdentity ? source.Id : source.ParentId) .FirstOrDefault(); return permissions == null @@ -319,6 +278,5 @@ namespace Umbraco.Web.Models.Mapping : permissions.AssignedPermissions.Where(x => x.Length == 1).Select(x => x.ToUpperInvariant()[0]); } } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 5f2a8bfd1d..c3f9412401 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -1,13 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using System.Web.Routing; using AutoMapper; -using umbraco; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; @@ -29,22 +25,12 @@ namespace Umbraco.Web.Models.Mapping { //FROM IMedia TO MediaItemDisplay config.CreateMap() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember( - dto => dto.Icon, - expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember( - dto => dto.ContentTypeAlias, - expression => expression.MapFrom(content => content.ContentType.Alias)) + .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) + .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) + .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) - .ForMember( - dto => dto.Trashed, - expression => expression.MapFrom(content => content.Trashed)) - .ForMember( - dto => dto.ContentTypeName, - expression => expression.MapFrom(content => content.ContentType.Name)) + .ForMember(display => display.Trashed, expression => expression.MapFrom(content => content.Trashed)) + .ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) .ForMember(display => display.Properties, expression => expression.Ignore()) .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) .ForMember(display => display.Notifications, expression => expression.Ignore()) @@ -53,39 +39,29 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Updater, expression => expression.Ignore()) .ForMember(display => display.Alias, expression => expression.Ignore()) .ForMember(display => display.IsContainer, expression => expression.Ignore()) - .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()) + .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()) .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) .AfterMap((media, display) => AfterMap(media, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, applicationContext.ProfilingLogger.Logger)); //FROM IMedia TO ContentItemBasic config.CreateMap>() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember( - dto => dto.Icon, - expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember( - dto => dto.Trashed, - expression => expression.MapFrom(content => content.Trashed)) - .ForMember( - dto => dto.ContentTypeAlias, - expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember(x => x.Published, expression => expression.Ignore()) - .ForMember(x => x.Updater, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()); + .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) + .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) + .ForMember(dto => dto.Trashed, expression => expression.MapFrom(content => content.Trashed)) + .ForMember(dto => dto.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) + .ForMember(dto => dto.Published, expression => expression.Ignore()) + .ForMember(dto => dto.Updater, expression => expression.Ignore()) + .ForMember(dto => dto.Alias, expression => expression.Ignore()) + .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); //FROM IMedia TO ContentItemDto config.CreateMap>() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(x => x.Published, expression => expression.Ignore()) - .ForMember(x => x.Updater, expression => expression.Ignore()) - .ForMember(x => x.Icon, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()); + .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) + .ForMember(dto => dto.Published, expression => expression.Ignore()) + .ForMember(dto => dto.Updater, expression => expression.Ignore()) + .ForMember(dto => dto.Icon, expression => expression.Ignore()) + .ForMember(dto => dto.Alias, expression => expression.Ignore()) + .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); } private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, ILogger logger) @@ -155,28 +131,28 @@ namespace Umbraco.Web.Models.Mapping genericProperties.Add(link); } - TabsAndPropertiesResolver.MapGenericProperties(media, display, localizedText, genericProperties, properties => { + TabsAndPropertiesResolver.MapGenericProperties(media, display, localizedText, genericProperties, properties => + { if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) { - var docTypeLink = string.Format("#/settings/mediatypes/edit/{0}", media.ContentTypeId); + var mediaTypeLink = string.Format("#/settings/mediatypes/edit/{0}", media.ContentTypeId); - //Replace the doc type property - var docTypeProp = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - docTypeProp.Value = new List + //Replace the doctype property + var docTypeProperty = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProperty.Value = new List + { + new { - new - { - linkText = media.ContentType.Name, - url = docTypeLink, - target = "_self", icon = "icon-item-arrangement" - } - }; - docTypeProp.View = "urllist"; + linkText = media.ContentType.Name, + url = mediaTypeLink, + target = "_self", + icon = "icon-item-arrangement" + } + }; + docTypeProperty.View = "urllist"; } - }); } - } } diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 6d4bdd7941..edb44d36ce 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -11,7 +11,6 @@ using Umbraco.Core.Models.Mapping; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; -using umbraco; using System.Linq; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Security; @@ -29,10 +28,10 @@ namespace Umbraco.Web.Models.Mapping //FROM MembershipUser TO MediaItemDisplay - used when using a non-umbraco membership provider config.CreateMap() .ConvertUsing(user => - { - var member = Mapper.Map(user); - return Mapper.Map(member); - }); + { + var member = Mapper.Map(user); + return Mapper.Map(member); + }); //FROM MembershipUser TO IMember - used when using a non-umbraco membership provider config.CreateMap() @@ -62,23 +61,13 @@ namespace Umbraco.Web.Models.Mapping //FROM IMember TO MediaItemDisplay config.CreateMap() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember( - dto => dto.Icon, - expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember( - dto => dto.ContentTypeAlias, - expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember( - dto => dto.ContentTypeName, - expression => expression.MapFrom(content => content.ContentType.Name)) + .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) + .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) + .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) + .ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) .ForMember(display => display.Properties, expression => expression.Ignore()) - .ForMember(display => display.Tabs, - expression => expression.ResolveUsing(new MemberTabsAndPropertiesResolver(applicationContext.Services.TextService))) - .ForMember(display => display.MemberProviderFieldMapping, - expression => expression.ResolveUsing(new MemberProviderFieldMappingResolver())) + .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new MemberTabsAndPropertiesResolver(applicationContext.Services.TextService))) + .ForMember(display => display.MemberProviderFieldMapping, expression => expression.ResolveUsing(new MemberProviderFieldMappingResolver())) .ForMember(display => display.MembershipScenario, expression => expression.ResolveUsing(new MembershipScenarioMappingResolver(new Lazy(() => applicationContext.Services.MemberTypeService)))) .ForMember(display => display.Notifications, expression => expression.Ignore()) @@ -90,31 +79,21 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Trashed, expression => expression.Ignore()) .ForMember(display => display.IsContainer, expression => expression.Ignore()) .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) - .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()) + .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()) .AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, member, display, applicationContext.Services.TextService)); //FROM IMember TO MemberBasic config.CreateMap() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember( - dto => dto.Icon, - expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember( - dto => dto.ContentTypeAlias, - expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember( - dto => dto.Email, - expression => expression.MapFrom(content => content.Email)) - .ForMember( - dto => dto.Username, - expression => expression.MapFrom(content => content.Username)) - .ForMember(display => display.Trashed, expression => expression.Ignore()) - .ForMember(x => x.Published, expression => expression.Ignore()) - .ForMember(x => x.Updater, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()); + .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) + .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) + .ForMember(dto => dto.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) + .ForMember(dto => dto.Email, expression => expression.MapFrom(content => content.Email)) + .ForMember(dto => dto.Username, expression => expression.MapFrom(content => content.Username)) + .ForMember(dto => dto.Trashed, expression => expression.Ignore()) + .ForMember(dto => dto.Published, expression => expression.Ignore()) + .ForMember(dto => dto.Updater, expression => expression.Ignore()) + .ForMember(dto => dto.Alias, expression => expression.Ignore()) + .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); //FROM MembershipUser TO MemberBasic config.CreateMap() @@ -123,41 +102,31 @@ namespace Umbraco.Web.Models.Mapping .ForMember(member => member.CreateDate, expression => expression.MapFrom(user => user.CreationDate)) .ForMember(member => member.UpdateDate, expression => expression.MapFrom(user => user.LastActivityDate)) .ForMember(member => member.Key, expression => expression.MapFrom(user => user.ProviderUserKey.TryConvertTo().Result.ToString("N"))) - .ForMember( - dto => dto.Owner, - expression => expression.UseValue(new UserBasic {Name = "Admin", UserId = 0})) - .ForMember( - dto => dto.Icon, - expression => expression.UseValue("icon-user")) + .ForMember(member => member.Owner, expression => expression.UseValue(new UserBasic {Name = "Admin", UserId = 0})) + .ForMember(member => member.Icon, expression => expression.UseValue("icon-user")) .ForMember(member => member.Name, expression => expression.MapFrom(user => user.UserName)) - .ForMember( - dto => dto.Email, - expression => expression.MapFrom(content => content.Email)) - .ForMember( - dto => dto.Username, - expression => expression.MapFrom(content => content.UserName)) + .ForMember(member => member.Email, expression => expression.MapFrom(content => content.Email)) + .ForMember(member => member.Username, expression => expression.MapFrom(content => content.UserName)) .ForMember(member => member.Properties, expression => expression.Ignore()) .ForMember(member => member.ParentId, expression => expression.Ignore()) .ForMember(member => member.Path, expression => expression.Ignore()) .ForMember(member => member.SortOrder, expression => expression.Ignore()) .ForMember(member => member.AdditionalData, expression => expression.Ignore()) - .ForMember(x => x.Published, expression => expression.Ignore()) - .ForMember(x => x.Updater, expression => expression.Ignore()) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(x => x.ContentTypeAlias, expression => expression.Ignore()) + .ForMember(member => member.Published, expression => expression.Ignore()) + .ForMember(member => member.Updater, expression => expression.Ignore()) + .ForMember(member => member.Trashed, expression => expression.Ignore()) + .ForMember(member => member.Alias, expression => expression.Ignore()) + .ForMember(member => member.ContentTypeAlias, expression => expression.Ignore()) .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()); //FROM IMember TO ContentItemDto config.CreateMap>() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(x => x.Published, expression => expression.Ignore()) - .ForMember(x => x.Updater, expression => expression.Ignore()) - .ForMember(x => x.Icon, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()) + .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) + .ForMember(dto => dto.Published, expression => expression.Ignore()) + .ForMember(dto => dto.Updater, expression => expression.Ignore()) + .ForMember(dto => dto.Icon, expression => expression.Ignore()) + .ForMember(dto => dto.Alias, expression => expression.Ignore()) + .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()) //do no map the custom member properties (currently anyways, they were never there in 6.x) .ForMember(dto => dto.Properties, expression => expression.ResolveUsing(new MemberDtoPropertiesValueResolver())); } @@ -232,25 +201,26 @@ namespace Umbraco.Web.Models.Mapping } }; - - TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties, properties => { + TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties, properties => + { if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) { - var docTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); + var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); - //Replace the doc type property - var docTypeProp = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - docTypeProp.Value = new List + //Replace the doctype property + var docTypeProperty = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProperty.Value = new List + { + new { - new - { - linkText = member.ContentType.Name, - url = docTypeLink, - target = "_self", icon = "icon-item-arrangement" - } - }; - docTypeProp.View = "urllist"; + linkText = member.ContentType.Name, + url = memberTypeLink, + target = "_self", + icon = "icon-item-arrangement" + } + }; + docTypeProperty.View = "urllist"; } }); @@ -265,7 +235,6 @@ namespace Umbraco.Web.Models.Mapping prop.Value = 1; } } - } /// @@ -274,6 +243,7 @@ namespace Umbraco.Web.Models.Mapping /// /// /// + /// /// /// /// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, however if @@ -283,11 +253,11 @@ namespace Umbraco.Web.Models.Mapping internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, MemberDisplay display, ILocalizedTextService localizedText) { var prop = new ContentPropertyDisplay - { - Alias = string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("login"), - Value = display.Username - }; + { + Alias = string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = localizedText.Localize("login"), + Value = display.Username + }; var scenario = memberService.GetMembershipScenario(); @@ -340,8 +310,8 @@ namespace Umbraco.Web.Models.Mapping var exclude = defaultProps.Select(x => x.Value.Alias).ToArray(); return source.Properties - .Where(x => exclude.Contains(x.Alias) == false) - .Select(Mapper.Map); + .Where(x => exclude.Contains(x.Alias) == false) + .Select(Mapper.Map); } } @@ -394,7 +364,7 @@ namespace Umbraco.Web.Models.Mapping } else { - var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider; + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; //This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier // if we just had all of the membeship provider fields on the member table :( @@ -408,8 +378,6 @@ namespace Umbraco.Web.Models.Mapping return result; } - - } } @@ -432,8 +400,8 @@ namespace Umbraco.Web.Models.Mapping } var memberType = _memberTypeService.Value.Get(Constants.Conventions.MemberTypes.DefaultAlias); return memberType != null - ? MembershipScenario.CustomProviderWithUmbracoLink - : MembershipScenario.StandaloneCustomProvider; + ? MembershipScenario.CustomProviderWithUmbracoLink + : MembershipScenario.StandaloneCustomProvider; } } @@ -457,7 +425,7 @@ namespace Umbraco.Web.Models.Mapping } else { - var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider; + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; return new Dictionary { @@ -466,10 +434,7 @@ namespace Umbraco.Web.Models.Mapping {Constants.Conventions.Member.Comments, umbracoProvider.CommentPropertyTypeAlias} }; } - - } } - } } \ No newline at end of file From cdb0c57416cc57679b25bf437a22c561e19c1804 Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 9 Nov 2016 11:48:32 +0100 Subject: [PATCH 57/88] readding the fix crgrieve did for treepicker.controller.js. --- .../src/views/common/dialogs/treepicker.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js index 2d8d38cf50..740791abf9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js @@ -259,7 +259,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", } }); } else { - var a = dialogOptions.filter.toLowerCase().split(','); + var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); angular.forEach(nodes, function (value, key) { var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; From b441c73604ec6e2266cbe6e72748e127bc297f74 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 9 Nov 2016 13:05:23 +0100 Subject: [PATCH 58/88] U4-9077 - relation type cache refresher + policy --- .../Repositories/RelationTypeRepository.cs | 69 ++++++++++--------- .../Cache/CacheRefresherEventHandler.cs | 27 +++++++- src/Umbraco.Web/Cache/DistributedCache.cs | 2 + .../Cache/DistributedCacheExtensions.cs | 14 ++++ .../Cache/RelationTypeCacheRefresher.cs | 52 ++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 6 files changed, 128 insertions(+), 37 deletions(-) create mode 100644 src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs index df0ae0b224..c0a110feca 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -18,50 +19,42 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class RelationTypeRepository : PetaPocoRepositoryBase, IRelationTypeRepository { - public RelationTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) + { } + + // assuming we don't have tons of relation types, use a FullDataSet policy, ie + // cache the entire GetAll result once in a single collection - which can expire + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory { + get + { + return _cachePolicyFactory + ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), expires: true)); + } } #region Overrides of RepositoryBase protected override IRelationType PerformGet(int id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - - var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); - if (dto == null) - return null; - - var factory = new RelationTypeFactory(); - var entity = factory.BuildEntity(dto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((TracksChangesEntityBase)entity).ResetDirtyProperties(false); - - return entity; + // use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) { + var sql = GetBaseQuery(false); + + // should not happen due to the cache policy if (ids.Any()) - { - foreach (var id in ids) - { - yield return Get(id); - } - } - else - { - var dtos = Database.Fetch("WHERE id > 0"); - foreach (var dto in dtos) - { - yield return Get(dto.Id); - } - } + throw new NotImplementedException(); + + var dtos = Database.Fetch(sql); + var factory = new RelationTypeFactory(); + return dtos.Select(x => DtoToEntity(x, factory)); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -71,11 +64,19 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate(); var dtos = Database.Fetch(sql); + var factory = new RelationTypeFactory(); + return dtos.Select(x => DtoToEntity(x, factory)); + } - foreach (var dto in dtos) - { - yield return Get(dto.Id); - } + private static IRelationType DtoToEntity(RelationTypeDto dto, RelationTypeFactory factory) + { + var entity = factory.BuildEntity(dto); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((TracksChangesEntityBase) entity).ResetDirtyProperties(false); + + return entity; } #endregion diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 15e01fd430..791318d8ab 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -125,9 +125,12 @@ namespace Umbraco.Web.Cache //public access events PublicAccessService.Saved += PublicAccessService_Saved; - PublicAccessService.Deleted += PublicAccessService_Deleted; ; + PublicAccessService.Deleted += PublicAccessService_Deleted; + + RelationService.SavedRelationType += RelationType_Saved; + RelationService.DeletedRelationType += RelationType_Deleted; } - + #region Publishing void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs e) @@ -661,7 +664,25 @@ namespace Umbraco.Web.Cache { DistributedCache.Instance.RemoveMemberGroupCache(m.Id); } - } + } + #endregion + + #region Relation type event handlers + + private static void RelationType_Saved(IRelationService sender, SaveEventArgs args) + { + var dc = DistributedCache.Instance; + foreach (var e in args.SavedEntities) + dc.RefreshRelationTypeCache(e.Id); + } + + private static void RelationType_Deleted(IRelationService sender, DeleteEventArgs args) + { + var dc = DistributedCache.Instance; + foreach (var e in args.DeletedEntities) + dc.RemoveRelationTypeCache(e.Id); + } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index 6848ce2496..01eaf4cdd3 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -38,6 +38,7 @@ namespace Umbraco.Web.Cache public const string ContentTypeCacheRefresherId = "6902E22C-9C10-483C-91F3-66B7CAE9E2F5"; public const string LanguageCacheRefresherId = "3E0F95D8-0BE5-44B8-8394-2B8750B62654"; public const string DomainCacheRefresherId = "11290A79-4B57-4C99-AD72-7748A3CF38AF"; + public const string RelationTypeCacheRefresherId = "D8375ABA-4FB3-4F86-B505-92FBA1B6F7C9"; [Obsolete("This is no longer used and will be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -67,6 +68,7 @@ namespace Umbraco.Web.Cache public static readonly Guid DataTypeCacheRefresherGuid = new Guid(DataTypeCacheRefresherId); public static readonly Guid DictionaryCacheRefresherGuid = new Guid(DictionaryCacheRefresherId); public static readonly Guid PublicAccessCacheRefresherGuid = new Guid(PublicAccessCacheRefresherId); + public static readonly Guid RelationTypeCacheRefresherGuid = new Guid(RelationTypeCacheRefresherId); #endregion diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index 750872d8af..50fd53ce09 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -446,5 +446,19 @@ namespace Umbraco.Web.Cache } #endregion + + #region Relation type cache + + public static void RefreshRelationTypeCache(this DistributedCache dc, int id) + { + dc.Refresh(DistributedCache.RelationTypeCacheRefresherGuid, id); + } + + public static void RemoveRelationTypeCache(this DistributedCache dc, int id) + { + dc.Remove(DistributedCache.RelationTypeCacheRefresherGuid, id); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs new file mode 100644 index 0000000000..cef308c52b --- /dev/null +++ b/src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs @@ -0,0 +1,52 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; + +namespace Umbraco.Web.Cache +{ + public sealed class RelationTypeCacheRefresher : CacheRefresherBase + { + protected override RelationTypeCacheRefresher Instance + { + get { return this; } + } + + public override Guid UniqueIdentifier + { + get { return DistributedCache.RelationTypeCacheRefresherGuid; } + } + + public override string Name + { + get { return "Relation Type Cache Refresher"; } + } + + public override void RefreshAll() + { + ClearAllIsolatedCacheByEntityType(); + base.RefreshAll(); + } + + public override void Refresh(int id) + { + var cache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (cache) cache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + base.Refresh(id); + } + + public override void Refresh(Guid id) + { + throw new NotSupportedException(); + //base.Refresh(id); + } + + public override void Remove(int id) + { + var cache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (cache) cache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + base.Remove(id); + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7fb022e848..a620a6716c 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -310,6 +310,7 @@ + From 3deda7efabf436d905cad69b6d7a7c164bd31de9 Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 9 Nov 2016 13:09:31 +0100 Subject: [PATCH 59/88] U4-7833 Changing doc type results in invalid cache, lucene indexes, etc... For now: add messages in the UI to tell people they need to publish the items again and rebuilt their indexes. --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 3 ++- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 ++ src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx | 4 +++- src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs | 3 +++ .../umbraco/dialogs/ChangeDocType.aspx.designer.cs | 9 +++++++++ 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index b0452fb631..b9996db11c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -109,9 +109,10 @@ Ny skabelon Ny type ingen + Du bør genopbygge dine Examine indekser via 'Examine Management' dashboarded i udvikler sektionen for at sikre at denne ændring registreres i hele dit website. Indhold Vælg ny dokumenttype - Dokumenttypen på detvalgte indhold blev skiftet til [new type], og følgende egenskaber blev overført: + Dokumenttypen på det valgte indhold blev skiftet til [new type], og følgende egenskaber blev overført: til Overførsel af egenskaber kunne ikke fuldføres, da en eller flere egenskaber er indstillet til at blive overført mere end én gang. Kun andre dokumenttyper, der er gyldige på denne placering, vises. diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index bdc2647563..317c044f9d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -100,6 +100,7 @@ Show styles Insert table Generate models + Save and generate models To change the document type for the selected content, first select from the list of valid types for this location. @@ -114,6 +115,7 @@ New Template New Type none + Please make sure to rebuild your Examine indexes using the 'Examine Management' dashboard in the Developer section, to ensure this change is applied everywhere. Content Select New Document Type The document type of the selected content has been successfully changed to [new type] and the following properties mapped: diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 7544cacc0c..7a39c59406 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -115,6 +115,7 @@ New Template New Type none + Please make sure to rebuild your Examine indexes using the 'Examine Management' dashboard in the Developer section, to ensure this change is applied everywhere. Content Select New Document Type The document type of the selected content has been successfully changed to [new type] and the following properties mapped: diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx index 77deab2709..a11c5cab49 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx @@ -97,7 +97,9 @@

    - <%=umbraco.ui.Text("defaultdialogs", "closeThisWindow") %> + +

    + <%=umbraco.ui.Text("defaultdialogs", "closeThisWindow") %>

    diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs index 0db3b6c36e..5d9243f994 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs @@ -265,10 +265,13 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs { ContentPublishedMessage.Text = global::umbraco.ui.Text("changeDocType", "contentRepublished"); ContentPublishedMessage.Visible = true; + RebuildIndexesMessage.Text = global::umbraco.ui.Text("changeDocType", "rebuildIndexes"); + RebuildIndexesMessage.Visible = true; } else { ContentPublishedMessage.Visible = false; + RebuildIndexesMessage.Visible = false; } SuccessPlaceholder.Visible = true; SaveAndCancelPlaceholder.Visible = false; diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.designer.cs b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.designer.cs index b774a3ff79..5fa673836a 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.designer.cs +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.designer.cs @@ -165,6 +165,15 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs { /// protected global::System.Web.UI.WebControls.Literal ContentPublishedMessage; + /// + /// RebuildIndexesMessage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal RebuildIndexesMessage; + /// /// ValidationPlaceholder control. /// From 5c3d605d34b0e44e71a60f43c95abfd39adb5143 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 9 Nov 2016 15:33:28 +0100 Subject: [PATCH 60/88] U4-9077 - in addition, deal with N+1 for relations --- .../Repositories/RelationRepository.cs | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs index be0808bb19..4511ebe35d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs @@ -42,34 +42,17 @@ namespace Umbraco.Core.Persistence.Repositories throw new Exception(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType)); var factory = new RelationFactory(relationType); - var entity = factory.BuildEntity(dto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((TracksChangesEntityBase)entity).ResetDirtyProperties(false); - - return entity; + return DtoToEntity(dto, factory); } - //TODO: Fix N+1 ! - protected override IEnumerable PerformGetAll(params int[] ids) { - if (ids.Any()) - { - foreach (var id in ids) - { - yield return Get(id); - } - } - else - { - var dtos = Database.Fetch("WHERE id > 0"); - foreach (var dto in dtos) - { - yield return Get(dto.Id); - } - } + var sql = GetBaseQuery(false); + if (ids.Length > 0) + sql.WhereIn(x => x.Id, ids); + sql.OrderBy(x => x.RelationType); + var dtos = Database.Fetch(sql); + return DtosToEntities(dtos); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -77,13 +60,36 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - + sql.OrderBy(x => x.RelationType); var dtos = Database.Fetch(sql); + return DtosToEntities(dtos); + } - foreach (var dto in dtos) + private IEnumerable DtosToEntities(IEnumerable dtos) + { + // in most cases, the relation type will be the same for all of them, + // plus we've ordered the relations by type, so try to allocate as few + // factories as possible - bearing in mind that relation types are cached + RelationFactory factory = null; + var relationTypeId = -1; + + return dtos.Select(x => { - yield return Get(dto.Id); - } + if (relationTypeId != x.RelationType) + factory = new RelationFactory(_relationTypeRepository.Get(relationTypeId = x.RelationType)); + return DtoToEntity(x, factory); + }); + } + + private static IRelation DtoToEntity(RelationDto dto, RelationFactory factory) + { + var entity = factory.BuildEntity(dto); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((TracksChangesEntityBase)entity).ResetDirtyProperties(false); + + return entity; } #endregion From 42522f5af3476787c2c6f9bcf558dd502a2984f2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 9 Nov 2016 16:45:06 +0100 Subject: [PATCH 61/88] U4-6994 - remove N+1 in ContentRepository.GetByPublishedVersion, .GetAllVersions --- .../Repositories/ContentRepository.cs | 84 ++++++++++++++----- .../Repositories/RelationRepository.cs | 2 - 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 84caeb2a3e..5d8818e1ac 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -254,6 +254,14 @@ namespace Umbraco.Core.Persistence.Repositories } while (processed < total); } + public override IEnumerable GetAllVersions(int id) + { + var sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { Id = id }) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + return GetAllBySql(sql); + } + public override IContent GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); @@ -662,28 +670,64 @@ namespace Umbraco.Core.Persistence.Repositories .OrderBy(x => x.Level, SqlSyntax) .OrderBy(x => x.SortOrder, SqlSyntax); - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - foreach (var dto in dtos) - { - //Check in the cache first. If it exists there AND it is published - // then we can use that entity. Otherwise if it is not published (which can be the case - // because we only store the 'latest' entries in the cache which might not be the published - // version) - var fromCache = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - //var fromCache = TryGetFromCache(dto.NodeId); - if (fromCache != null && fromCache.Published) - { - yield return fromCache; - } - else - { - yield return CreateContentFromDto(dto, dto.VersionId, sql); - } - } + return GetAllBySql(sql); } + private IEnumerable GetAllBySql(Sql sql) + { + var dtos = Database.Fetch(sql); + var content = new IContent[dtos.Count]; + var defs = new List(); + + for (var i = 0; i < dtos.Count; i++) + { + var dto = dtos[i]; + + // if the cache contains the published version, use it + var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + if (cached != null && cached.Published) + { + content[i] = cached; + continue; + } + + // else, need to fetch the version from the database + var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); + var c = factory.BuildEntity(dto); + if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + c.Template = _templateRepository.Get(dto.TemplateId.Value); + content[i] = c; + + defs.Add(new DocumentDefinition( + dto.NodeId, + dto.VersionId, + dto.ContentVersionDto.VersionDate, + dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + contentType + )); + } + + // going to load all properties for all docs from database, + // but at least in one queries thus avoiding N+1 + var propertyData = GetPropertyCollection(sql, defs); + + var dtoIndex = 0; + var defIndex = 0; + while (true) + { + if (defIndex == defs.Count) return content; + while (dtoIndex < dtos.Count && dtos[dtoIndex].NodeId != defs[defIndex].Id) dtoIndex++; + var cc = content[dtoIndex]; + cc.Properties = propertyData[cc.Id]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)cc).ResetDirtyProperties(false); + + defIndex++; + } + } /// /// This builds the Xml document used for the XML cache diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs index be0808bb19..430261bf0e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs @@ -51,8 +51,6 @@ namespace Umbraco.Core.Persistence.Repositories return entity; } - //TODO: Fix N+1 ! - protected override IEnumerable PerformGetAll(params int[] ids) { if (ids.Any()) From e77bddd0f216f6eaafd7e6e1f59e3f6d02cd7253 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 10 Nov 2016 11:08:28 +0100 Subject: [PATCH 62/88] add filtering of media types --- .../services/mediatypehelper.service.js | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js index 919a803456..20e5e3799b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js @@ -19,22 +19,38 @@ function mediaTypeHelper(mediaTypeResource, $q) { // Get full list return $q.all(allowedQ).then(function(fullTypes){ - // Only mediatypes with 'umbracoFile' property - return fullTypes.filter(function(mediatype){ - for(var i = 0; i < mediatype.groups.length; i++){ - var group = mediatype.groups[i]; - for(var j = 0; j < group.properties.length; j++){ - var property = group.properties[j]; - if(property.editor === 'Umbraco.ImageCropper' || property.editor === 'Umbraco.UploadField'){ - return mediatype; - } - } - } - }); + // Find all the media types with an Image Cropper property editor + var filteredTypes = mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper']); + + // If there is only one media type with an Image Cropper we will return this one + if(filteredTypes.length === 1) { + return filteredTypes; + // If there is more than one Image cropper, custom media types have been added, and we return all media types with and Image cropper or UploadField + } else { + return mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper', 'Umbraco.UploadField']); + } + }); }); - } + }, + + getTypeWithEditor: function (types, editors) { + + return types.filter(function (mediatype) { + for (var i = 0; i < mediatype.groups.length; i++) { + var group = mediatype.groups[i]; + for (var j = 0; j < group.properties.length; j++) { + var property = group.properties[j]; + if( editors.indexOf(property.editor) !== -1 ) { + return mediatype; + } + } + } + }); + + } + }; return mediaTypeHelperService; From 07c2a1432183e9dfc6f4cfd766320af64c2bd028 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 10 Nov 2016 11:13:00 +0100 Subject: [PATCH 63/88] If there is only 1 allowed media type set the alias to auto to let the server decide --- .../components/upload/umbfiledropzone.directive.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index 52704855bb..fdf949a613 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -104,9 +104,10 @@ angular.module("umbraco.directives") scope.queue = []; } - // One allowed mediaType, pick this one + // One allowed mediaType if(scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1){ - scope.contentTypeAlias = scope.acceptedMediatypes[0].alias; + // set alias to auto to let the server best decide which media type to use + scope.contentTypeAlias = "umbracoAuto"; _processQueueItem(); } From f9d8754d888cfd7dbf46df4e4af8cc33fbf1ef19 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 10 Nov 2016 12:00:07 +0100 Subject: [PATCH 64/88] add support for one custom media type --- .../components/upload/umbfiledropzone.directive.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index fdf949a613..65e35a16d1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -104,10 +104,16 @@ angular.module("umbraco.directives") scope.queue = []; } - // One allowed mediaType - if(scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1){ - // set alias to auto to let the server best decide which media type to use - scope.contentTypeAlias = "umbracoAuto"; + // One allowed type + if(scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1) { + + // Standard setup - set alias to auto select to let the server best decide which media type to use + if(scope.acceptedMediatypes[0].alias === 'Image') { + scope.contentTypeAlias = "umbracoAutoSelect"; + } else { + scope.contentTypeAlias = scope.acceptedMediatypes[0].alias; + } + _processQueueItem(); } From 4f5bc9eb89d672befe82b8d0c83443e96ea72e23 Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 10 Nov 2016 12:36:52 +0100 Subject: [PATCH 65/88] updating MediaController with AutoSelect option. --- src/Umbraco.Core/Constants-Conventions.cs | 5 +++++ src/Umbraco.Web/Editors/MediaController.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 7e2bb88964..d7f4576137 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -122,6 +122,11 @@ namespace Umbraco.Core /// MediaType alias for an image. /// public const string Image = "Image"; + + /// + /// MediaType alias indicating allowing auto-selection. + /// + public const string AutoSelect = "umbracoAutoSelect"; } /// diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 932b3f6766..6604aee507 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -531,7 +531,7 @@ namespace Umbraco.Web.Editors { var mediaType = Constants.Conventions.MediaTypes.File; - if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.Image) + if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.AutoSelect) { if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) { From 9e24b694b3c1ca7754d4f3177a453ab7fbac36aa Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 10 Nov 2016 13:09:22 +0100 Subject: [PATCH 66/88] adding migration to retain functionality as it has always been in root when upgrading. --- .../UpdateAllowedMediaTypesAtRoot.cs | 25 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 2 files changed, 26 insertions(+) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs new file mode 100644 index 0000000000..565b7f6f13 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs @@ -0,0 +1,25 @@ +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive +{ + /// + /// See: http://issues.umbraco.org/issue/U4-4196 + /// + [Migration("7.5.5", 1, GlobalSettings.UmbracoMigrationName)] + public class UpdateAllowedMediaTypesAtRoot : MigrationBase + { + public UpdateAllowedMediaTypesAtRoot(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { } + + public override void Up() + { + Execute.Sql("UPDATE cmsContentType SET allowAtRoot = 1 WHERE nodeId = 1031 OR nodeId = 1032 OR nodeId = 1033"); + } + + public override void Down() + { } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c28cea24cd..d6b2e0c438 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -429,6 +429,7 @@ + From 2115367353e8e9ef6373209780fd10e5fe0e1995 Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 14 Nov 2016 09:57:17 +0100 Subject: [PATCH 67/88] updating the migration to include only needed ids. formatting stuff in js. --- .../UpdateAllowedMediaTypesAtRoot.cs | 2 +- .../upload/umbfiledropzone.directive.js | 444 ++++++++---------- .../common/dialogs/mediapicker.controller.js | 75 +-- .../mediaPicker/mediapicker.controller.js | 230 ++++----- 4 files changed, 338 insertions(+), 413 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs index 565b7f6f13..c9a0d509e6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFiv public override void Up() { - Execute.Sql("UPDATE cmsContentType SET allowAtRoot = 1 WHERE nodeId = 1031 OR nodeId = 1032 OR nodeId = 1033"); + Execute.Sql("UPDATE cmsContentType SET allowAtRoot = 1 WHERE nodeId = 1032 OR nodeId = 1033"); } public override void Down() diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index 65e35a16d1..6cafa05bc8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -10,269 +10,223 @@ /* TODO .directive("umbFileDrop", function ($timeout, $upload, localizationService, umbRequestHelper){ - - return{ - restrict: "A", - link: function(scope, element, attrs){ - - //load in the options model - - - } - } + return{ + restrict: "A", + link: function(scope, element, attrs){ + //load in the options model + } + } }) */ angular.module("umbraco.directives") + .directive('umbFileDropzone', + function($timeout, Upload, localizationService, umbRequestHelper) { + return { + restrict: 'E', + replace: true, + templateUrl: 'views/components/upload/umb-file-dropzone.html', + scope: { + parentId: '@', + contentTypeAlias: '@', + propertyAlias: '@', + accept: '@', + maxFileSize: '@', -.directive('umbFileDropzone', function ($timeout, Upload, localizationService, umbRequestHelper, editorState) { - return { + compact: '@', + hideDropzone: '@', + acceptedMediatypes: '=', - restrict: 'E', - replace: true, + filesQueued: '=', + handleFile: '=', + filesUploaded: '=' + }, + link: function(scope, element, attrs) { + scope.queue = []; + scope.done = []; + scope.rejected = []; + scope.currentFile = undefined; - templateUrl: 'views/components/upload/umb-file-dropzone.html', + function _filterFile(file) { + var ignoreFileNames = ['Thumbs.db']; + var ignoreFileTypes = ['directory']; - scope: { - parentId: '@', - contentTypeAlias: '@', - propertyAlias: '@', - accept: '@', - maxFileSize: '@', + // ignore files with names from the list + // ignore files with types from the list + // ignore files which starts with "." + if (ignoreFileNames.indexOf(file.name) === -1 && + ignoreFileTypes.indexOf(file.type) === -1 && + file.name.indexOf(".") !== 0) { + return true; + } else { + return false; + } + } - compact: '@', - hideDropzone: '@', - acceptedMediatypes: '=', + function _filesQueued(files, event) { + //Push into the queue + angular.forEach(files, + function(file) { - filesQueued: '=', - handleFile: '=', - filesUploaded: '=' - }, + if (_filterFile(file) === true) { - link: function(scope, element, attrs) { - - scope.queue = []; - scope.done = []; - scope.rejected = []; + if (file.$error) { + scope.rejected.push(file); + } else { + scope.queue.push(file); + } + } + }); - scope.currentFile = undefined; + //when queue is done, kick the uploader + if (!scope.working) { + // Upload not allowed + if (!scope.acceptedMediatypes || !scope.acceptedMediatypes.length) { + files.map(function(file) { + file.uploadStatus = "error"; + file.serverErrorMessage = "File type is not allowed here"; + scope.rejected.push(file); + }); + scope.queue = []; + } + // One allowed type + if (scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1) { + // Standard setup - set alias to auto select to let the server best decide which media type to use + if (scope.acceptedMediatypes[0].alias === 'Image') { + scope.contentTypeAlias = "umbracoAutoSelect"; + } else { + scope.contentTypeAlias = scope.acceptedMediatypes[0].alias; + } - function _filterFile(file) { - - var ignoreFileNames = ['Thumbs.db']; - var ignoreFileTypes = ['directory']; + _processQueueItem(); + } + // More than one, open dialog + if (scope.acceptedMediatypes && scope.acceptedMediatypes.length > 1) { + _chooseMediaType(); + } + } + } - // ignore files with names from the list - // ignore files with types from the list - // ignore files which starts with "." - if(ignoreFileNames.indexOf(file.name) === -1 && - ignoreFileTypes.indexOf(file.type) === -1 && - file.name.indexOf(".") !== 0) { - return true; - } else { - return false; - } + function _processQueueItem() { + if (scope.queue.length > 0) { + scope.currentFile = scope.queue.shift(); + _upload(scope.currentFile); + } else if (scope.done.length > 0) { + if (scope.filesUploaded) { + //queue is empty, trigger the done action + scope.filesUploaded(scope.done); + } - } + //auto-clear the done queue after 3 secs + var currentLength = scope.done.length; + $timeout(function() { + scope.done.splice(0, currentLength); + }, + 3000); + } + } - function _filesQueued(files, event){ + function _upload(file) { - //Push into the queue - angular.forEach(files, function(file){ + scope.propertyAlias = scope.propertyAlias ? scope.propertyAlias : "umbracoFile"; + scope.contentTypeAlias = scope.contentTypeAlias ? scope.contentTypeAlias : "Image"; - if(_filterFile(file) === true) { + Upload.upload({ + url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile"), + fields: { + 'currentFolder': scope.parentId, + 'contentTypeAlias': scope.contentTypeAlias, + 'propertyAlias': scope.propertyAlias, + 'path': file.path + }, + file: file + }) + .progress(function(evt) { + // calculate progress in percentage + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); + // set percentage property on file + file.uploadProgress = progressPercentage; + // set uploading status on file + file.uploadStatus = "uploading"; + }) + .success(function(data, status, headers, config) { + if (data.notifications && data.notifications.length > 0) { + // set error status on file + file.uploadStatus = "error"; + // Throw message back to user with the cause of the error + file.serverErrorMessage = data.notifications[0].message; + // Put the file in the rejected pool + scope.rejected.push(file); + } else { + // set done status on file + file.uploadStatus = "done"; + // set date/time for when done - used for sorting + file.doneDate = new Date(); + // Put the file in the done pool + scope.done.push(file); + } + scope.currentFile = undefined; + //after processing, test if everthing is done + _processQueueItem(); + }) + .error(function(evt, status, headers, config) { + // set status done + file.uploadStatus = "error"; + //if the service returns a detailed error + if (evt.InnerException) { + file.serverErrorMessage = evt.InnerException.ExceptionMessage; + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && + evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + file.serverErrorMessage = "File too large to upload"; + } + } else if (evt.Message) { + file.serverErrorMessage = evt.Message; + } + // If file not found, server will return a 404 and display this message + if (status === 404) { + file.serverErrorMessage = "File not found"; + } + //after processing, test if everthing is done + scope.rejected.push(file); + scope.currentFile = undefined; + _processQueueItem(); + }); + } - if(file.$error) { - scope.rejected.push(file); - } else { - scope.queue.push(file); - } - } + function _chooseMediaType() { + scope.mediatypepickerOverlay = { + view: "mediatypepicker", + title: "Choose media type", + acceptedMediatypes: scope.acceptedMediatypes, + hideSubmitButton: true, + show: true, + submit: function(model) { + scope.contentTypeAlias = model.selectedType.alias; + scope.mediatypepickerOverlay.show = false; + scope.mediatypepickerOverlay = null; + _processQueueItem(); + }, + close: function(oldModel) { - }); - - //when queue is done, kick the uploader - if(!scope.working){ + scope.queue.map(function(file) { + file.uploadStatus = "error"; + file.serverErrorMessage = "Cannot upload this file, no mediatype selected"; + scope.rejected.push(file); + }); + scope.queue = []; + scope.mediatypepickerOverlay.show = false; + scope.mediatypepickerOverlay = null; + } + }; + } - // Upload not allowed - if(!scope.acceptedMediatypes || !scope.acceptedMediatypes.length){ - files.map(function(file){ - file.uploadStatus = "error"; - file.serverErrorMessage = "File type is not allowed here"; - scope.rejected.push(file); - }); - scope.queue = []; - } - - // One allowed type - if(scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1) { - - // Standard setup - set alias to auto select to let the server best decide which media type to use - if(scope.acceptedMediatypes[0].alias === 'Image') { - scope.contentTypeAlias = "umbracoAutoSelect"; - } else { - scope.contentTypeAlias = scope.acceptedMediatypes[0].alias; - } - - _processQueueItem(); - } - - // More than one, open dialog - if(scope.acceptedMediatypes && scope.acceptedMediatypes.length > 1){ - _chooseMediaType(); - } - } - } - - function _processQueueItem(){ - - if(scope.queue.length > 0){ - scope.currentFile = scope.queue.shift(); - _upload(scope.currentFile); - }else if(scope.done.length > 0){ - - if(scope.filesUploaded){ - //queue is empty, trigger the done action - scope.filesUploaded(scope.done); - } - - //auto-clear the done queue after 3 secs - var currentLength = scope.done.length; - $timeout(function(){ - scope.done.splice(0, currentLength); - }, 3000); - } - } - - function _upload(file) { - - scope.propertyAlias = scope.propertyAlias ? scope.propertyAlias : "umbracoFile"; - scope.contentTypeAlias = scope.contentTypeAlias ? scope.contentTypeAlias : "Image"; - - Upload.upload({ - url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile"), - fields: { - 'currentFolder': scope.parentId, - 'contentTypeAlias': scope.contentTypeAlias, - 'propertyAlias': scope.propertyAlias, - 'path': file.path - }, - file: file - }).progress(function (evt) { - - // calculate progress in percentage - var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); - - // set percentage property on file - file.uploadProgress = progressPercentage; - - // set uploading status on file - file.uploadStatus = "uploading"; - - }).success(function (data, status, headers, config) { - - if(data.notifications && data.notifications.length > 0) { - - // set error status on file - file.uploadStatus = "error"; - - // Throw message back to user with the cause of the error - file.serverErrorMessage = data.notifications[0].message; - - // Put the file in the rejected pool - scope.rejected.push(file); - - } else { - - // set done status on file - file.uploadStatus = "done"; - - // set date/time for when done - used for sorting - file.doneDate = new Date(); - - // Put the file in the done pool - scope.done.push(file); - - } - - scope.currentFile = undefined; - - //after processing, test if everthing is done - _processQueueItem(); - - }).error( function (evt, status, headers, config) { - - // set status done - file.uploadStatus = "error"; - - //if the service returns a detailed error - if (evt.InnerException) { - file.serverErrorMessage = evt.InnerException.ExceptionMessage; - - //Check if its the common "too large file" exception - if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { - file.serverErrorMessage = "File too large to upload"; - } - - } else if (evt.Message) { - file.serverErrorMessage = evt.Message; - } - - // If file not found, server will return a 404 and display this message - if(status === 404 ) { - file.serverErrorMessage = "File not found"; - } - - //after processing, test if everthing is done - scope.rejected.push(file); - scope.currentFile = undefined; - - _processQueueItem(); - }); - } - - function _chooseMediaType() { - - scope.mediatypepickerOverlay = { - view: "mediatypepicker", - title: "Choose media type", - acceptedMediatypes: scope.acceptedMediatypes, - hideSubmitButton: true, - show: true, - submit: function(model) { - scope.contentTypeAlias = model.selectedType.alias; - - scope.mediatypepickerOverlay.show = false; - scope.mediatypepickerOverlay = null; - - _processQueueItem(); - }, - close: function(oldModel) { - - scope.queue.map(function(file){ - file.uploadStatus = "error"; - file.serverErrorMessage = "Cannot upload this file, no mediatype selected"; - scope.rejected.push(file); - }); - scope.queue = []; - - scope.mediatypepickerOverlay.show = false; - scope.mediatypepickerOverlay = null; - - } - }; - - } - - scope.handleFiles = function(files, event){ - if(scope.filesQueued){ - scope.filesQueued(files, event); - } - - _filesQueued(files, event); - - }; - - } - }; -}); + scope.handleFiles = function(files, event) { + if (scope.filesQueued) { + scope.filesQueued(files, event); + } + _filesQueued(files, event); + }; + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js index 504f547182..f337dbce18 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Dialogs.MediaPickerController", - function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $cookies, $element, $timeout) { + function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService) { var dialogOptions = $scope.dialogOptions; @@ -13,31 +13,32 @@ angular.module("umbraco") //preload selected item $scope.target = undefined; - if(dialogOptions.currentTarget){ + if (dialogOptions.currentTarget) { $scope.target = dialogOptions.currentTarget; } $scope.acceptedMediatypes = []; - mediaTypeHelper.getAllowedImagetypes($scope.startNodeId).then(function(types){ - $scope.acceptedMediatypes = types; - }); + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); - $scope.upload = function(v){ - angular.element(".umb-file-dropzone-directive .file-select").click(); + $scope.upload = function(v) { + angular.element(".umb-file-dropzone-directive .file-select").click(); }; - $scope.dragLeave = function(el, event){ + $scope.dragLeave = function(el, event) { $scope.activeDrag = false; }; - $scope.dragEnter = function(el, event){ + $scope.dragEnter = function(el, event) { $scope.activeDrag = true; }; $scope.submitFolder = function(e) { if (e.keyCode === 13) { e.preventDefault(); - + mediaResource .addFolder($scope.newFolderName, $scope.currentFolder.id) .then(function(data) { @@ -56,25 +57,25 @@ angular.module("umbraco") }; $scope.gotoFolder = function(folder) { - - if(!folder){ - folder = {id: -1, name: "Media", icon: "icon-folder"}; + if (!folder) { + folder = { id: -1, name: "Media", icon: "icon-folder" }; } if (folder.id > 0) { entityResource.getAncestors(folder.id, "media") .then(function(anc) { // anc.splice(0,1); - $scope.path = _.filter(anc, function (f) { - return f.path.indexOf($scope.startNodeId) !== -1; - }); + $scope.path = _.filter(anc, + function(f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); }); - mediaTypeHelper.getAllowedImagetypes(folder.id).then(function(types){ - $scope.acceptedMediatypes = types; - }); - } - else { + mediaTypeHelper.getAllowedImagetypes(folder.id) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); + } else { $scope.path = []; } @@ -84,50 +85,50 @@ angular.module("umbraco") $scope.searchTerm = ""; $scope.images = data.items ? data.items : []; }); - - $scope.currentFolder = folder; + + $scope.currentFolder = folder; }; - - + + $scope.clickHandler = function(image, ev, select) { ev.preventDefault(); - + if (image.isFolder && !select) { $scope.gotoFolder(image); - }else{ + } else { eventsService.emit("dialogs.mediaPicker.select", image); - + //we have 3 options add to collection (if multi) show details, or submit it right back to the callback if ($scope.multiPicker) { $scope.select(image); image.cssclass = ($scope.dialogData.selection.indexOf(image) > -1) ? "selected" : ""; - }else if($scope.showDetails) { - $scope.target= image; + } else if ($scope.showDetails) { + $scope.target = image; $scope.target.url = mediaHelper.resolveFile(image); - }else{ + } else { $scope.submit(image); } } }; - $scope.exitDetails = function(){ - if(!$scope.currentFolder){ + $scope.exitDetails = function() { + if (!$scope.currentFolder) { $scope.gotoFolder(); } $scope.target = undefined; }; - $scope.onUploadComplete = function () { + $scope.onUploadComplete = function() { $scope.gotoFolder($scope.currentFolder); }; - $scope.onFilesQueue = function(){ + $scope.onFilesQueue = function() { $scope.activeDrag = false; }; //default root item - if(!$scope.target){ - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + if (!$scope.target) { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); } }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index 27bc380fd3..43c65d99ee 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -1,9 +1,9 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Overlays.MediaPickerController", - function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, $cookies, $cookieStore, localizationService) { + function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, $cookies, $cookieStore, localizationService) { - if(!$scope.model.title) { + if (!$scope.model.title) { $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); } @@ -16,65 +16,59 @@ angular.module("umbraco") $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; $scope.cropSize = dialogOptions.cropSize; $scope.lastOpenedNode = $cookieStore.get("umbLastOpenedMediaNodeId"); - if($scope.onlyImages){ - $scope.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); - } - else { - $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); + if ($scope.onlyImages) { + $scope.acceptedFileTypes = mediaHelper + .formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); + } else { + $scope.acceptedFileTypes = !mediaHelper + .formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); } $scope.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; $scope.model.selectedImages = []; $scope.acceptedMediatypes = []; - mediaTypeHelper.getAllowedImagetypes($scope.startNodeId).then(function(types){ - $scope.acceptedMediatypes = types; - }); + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); //preload selected item $scope.target = undefined; - if(dialogOptions.currentTarget){ + if (dialogOptions.currentTarget) { $scope.target = dialogOptions.currentTarget; } - $scope.upload = function(v){ - angular.element(".umb-file-dropzone-directive .file-select").click(); + $scope.upload = function(v) { + angular.element(".umb-file-dropzone-directive .file-select").click(); }; - $scope.dragLeave = function(el, event){ + $scope.dragLeave = function(el, event) { $scope.activeDrag = false; }; - $scope.dragEnter = function(el, event){ + $scope.dragEnter = function(el, event) { $scope.activeDrag = true; }; $scope.submitFolder = function() { + if ($scope.newFolderName) { + mediaResource + .addFolder($scope.newFolderName, $scope.currentFolder.id) + .then(function(data) { + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: "__media", //this is the main media tree cache key + childrenOf: data.parentId //clear the children of the parent + }); - if ($scope.newFolderName) { - - mediaResource - .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { - - //we've added a new folder so lets clear the tree cache for that specific item - treeService.clearCache({ - cacheKey: "__media", //this is the main media tree cache key - childrenOf: data.parentId //clear the children of the parent + $scope.gotoFolder(data); + $scope.showFolderInput = false; + $scope.newFolderName = ""; }); - - $scope.gotoFolder(data); - - $scope.showFolderInput = false; - - $scope.newFolderName = ""; - - }); - - } else { - $scope.showFolderInput = false; - } - + } else { + $scope.showFolderInput = false; + } }; $scope.enterSubmitFolder = function(event) { @@ -86,62 +80,61 @@ angular.module("umbraco") $scope.gotoFolder = function(folder) { - if(!$scope.multiPicker) { + if (!$scope.multiPicker) { deselectAllImages($scope.model.selectedImages); } - if(!folder){ - folder = {id: -1, name: "Media", icon: "icon-folder"}; + if (!folder) { + folder = { id: -1, name: "Media", icon: "icon-folder" }; } if (folder.id > 0) { entityResource.getAncestors(folder.id, "media") .then(function(anc) { // anc.splice(0,1); - $scope.path = _.filter(anc, function (f) { - return f.path.indexOf($scope.startNodeId) !== -1; - }); + $scope.path = _.filter(anc, + function(f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); }); - mediaTypeHelper.getAllowedImagetypes(folder.id).then(function(types){ + mediaTypeHelper.getAllowedImagetypes(folder.id) + .then(function(types) { $scope.acceptedMediatypes = types; }); - } - else { + } else { $scope.path = []; } //mediaResource.rootMedia() mediaResource.getChildren(folder.id) - .then(function(data) { - $scope.searchTerm = ""; - $scope.images = data.items ? data.items : []; + .then(function(data) { + $scope.searchTerm = ""; + $scope.images = data.items ? data.items : []; - // set already selected images to selected - for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { + // set already selected images to selected + for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { + var folderImage = $scope.images[folderImageIndex]; + var imageIsSelected = false; - var folderImage = $scope.images[folderImageIndex]; - var imageIsSelected = false; + for (var selectedImageIndex = 0; + selectedImageIndex < $scope.model.selectedImages.length; + selectedImageIndex++) { + var selectedImage = $scope.model.selectedImages[selectedImageIndex]; - for (var selectedImageIndex = 0; selectedImageIndex < $scope.model.selectedImages.length; selectedImageIndex++) { - var selectedImage = $scope.model.selectedImages[selectedImageIndex]; - - if(folderImage.key === selectedImage.key) { - imageIsSelected = true; + if (folderImage.key === selectedImage.key) { + imageIsSelected = true; + } } - } - - if(imageIsSelected) { - folderImage.selected = true; - } - } - - }); - + if (imageIsSelected) { + folderImage.selected = true; + } + } + }); $scope.currentFolder = folder; // for some reason i cannot set cookies with cookieStore - document.cookie="umbLastOpenedMediaNodeId=" + folder.id; + document.cookie = "umbLastOpenedMediaNodeId=" + folder.id; }; @@ -153,52 +146,40 @@ angular.module("umbraco") eventsService.emit("dialogs.mediaPicker.select", image); selectImage(image); } - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - - if($scope.showDetails) { + if ($scope.showDetails) { $scope.target = image; $scope.target.url = mediaHelper.resolveFile(image); $scope.openDetailsDialog(); } else { selectImage(image); } - } - }; $scope.clickItemName = function(item) { - if(item.isFolder) { + if (item.isFolder) { $scope.gotoFolder(item); } }; function selectImage(image) { - - if(image.selected) { - - for(var i = 0; $scope.model.selectedImages.length > i; i++) { - + if (image.selected) { + for (var i = 0; $scope.model.selectedImages.length > i; i++) { var imageInSelection = $scope.model.selectedImages[i]; - if(image.key === imageInSelection.key) { + if (image.key === imageInSelection.key) { image.selected = false; $scope.model.selectedImages.splice(i, 1); } } - } else { - - if(!$scope.multiPicker) { + if (!$scope.multiPicker) { deselectAllImages($scope.model.selectedImages); } - image.selected = true; $scope.model.selectedImages.push(image); } - } function deselectAllImages(images) { @@ -209,64 +190,53 @@ angular.module("umbraco") images.length = 0; } - $scope.onUploadComplete = function () { + $scope.onUploadComplete = function() { $scope.gotoFolder($scope.currentFolder); }; - $scope.onFilesQueue = function(){ + $scope.onFilesQueue = function() { $scope.activeDrag = false; }; //default root item if (!$scope.target) { + if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { + entityResource.getById($scope.lastOpenedNode, "media") + .then(function(node) { + // make sure that las opened node is on the same path as start node + var nodePath = node.path.split(","); - if($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { - - entityResource.getById($scope.lastOpenedNode, "media") - .then(function(node){ - - // make sure that las opened node is on the same path as start node - var nodePath = node.path.split(","); - - if(nodePath.indexOf($scope.startNodeId.toString()) !== -1) { - $scope.gotoFolder({id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder"}); - } else { - $scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"}); - } - - }, function (err) { - $scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"}); - }); - - } else { - - $scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"}); - - } - + if (nodePath.indexOf($scope.startNodeId.toString()) !== -1) { + $scope + .gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder" }); + } else { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + } + }, + function(err) { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + }); + } else { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + } } $scope.openDetailsDialog = function() { - $scope.mediaPickerDetailsOverlay = {}; - $scope.mediaPickerDetailsOverlay.show = true; + $scope.mediaPickerDetailsOverlay = {}; + $scope.mediaPickerDetailsOverlay.show = true; - $scope.mediaPickerDetailsOverlay.submit = function(model) { + $scope.mediaPickerDetailsOverlay.submit = function(model) { + $scope.model.selectedImages.push($scope.target); + $scope.model.submit($scope.model); - $scope.model.selectedImages.push($scope.target); - $scope.model.submit($scope.model); - - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - - }; - - $scope.mediaPickerDetailsOverlay.close = function(oldModel) { - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + $scope.mediaPickerDetailsOverlay.close = function(oldModel) { + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; }; - - }); From 199c5023969bbc7cdbd357cb3b65daf5d5ba811a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 14 Nov 2016 15:24:41 +0100 Subject: [PATCH 68/88] Get the correct types --- src/UmbracoExamine/UmbracoContentIndexer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index ce3583a6be..a9405b7755 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -419,8 +419,8 @@ namespace UmbracoExamine XElement[] mediaXElements; - var nodeTypes = _contentTypeService.GetAllContentTypes().ToArray(); - var icons = nodeTypes.ToDictionary(x => x.Id, y => y.Icon); + var mediaTypes = _contentTypeService.GetAllMediaTypes().ToArray(); + var icons = mediaTypes.ToDictionary(x => x.Id, y => y.Icon); do { @@ -443,7 +443,7 @@ namespace UmbracoExamine //TODO: Update the service layer to join the cmsContentType table so we can query by content type too if (IndexerData.IncludeNodeTypes.Any()) { - var includeNodeTypeIds = nodeTypes.Where(x => IndexerData.IncludeNodeTypes.Contains(x.Alias)).Select(x => x.Id); + var includeNodeTypeIds = mediaTypes.Where(x => IndexerData.IncludeNodeTypes.Contains(x.Alias)).Select(x => x.Id); mediaXElements = mediaXElements.Where(elm => includeNodeTypeIds.Contains(elm.AttributeValue("nodeType"))).ToArray(); } From 935d010dd2fd2d9e0acbbf15377dda741009a336 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 14 Nov 2016 15:26:25 +0100 Subject: [PATCH 69/88] The foreach is actually more readable --- src/UmbracoExamine/UmbracoContentIndexer.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index a9405b7755..301491c975 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -446,11 +446,10 @@ namespace UmbracoExamine var includeNodeTypeIds = mediaTypes.Where(x => IndexerData.IncludeNodeTypes.Contains(x.Alias)).Select(x => x.Id); mediaXElements = mediaXElements.Where(elm => includeNodeTypeIds.Contains(elm.AttributeValue("nodeType"))).ToArray(); } - - // ReSharper disable once ForCanBeConvertedToForeach - for (var i = 0; i < mediaXElements.Length; i++) + + foreach (var element in mediaXElements) { - mediaXElements[i].Add(new XAttribute("icon", icons[mediaXElements[i].AttributeValue("nodeType")])); + element.Add(new XAttribute("icon", icons[element.AttributeValue("nodeType")])); } AddNodesToIndex(mediaXElements, type); From 714b7019c5e5085d54e983ef49bbe5a02d400731 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 14 Nov 2016 15:38:50 +0100 Subject: [PATCH 70/88] Fix unit tests to have the correct types as well --- src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 89a9df8052..00e94ced63 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -159,11 +159,11 @@ namespace Umbraco.Tests.UmbracoExamine if (contentTypeService == null) { var contentTypeServiceMock = new Mock(); - contentTypeServiceMock.Setup(x => x.GetAllContentTypes()) - .Returns(new List() + contentTypeServiceMock.Setup(x => x.GetAllMediaTypes()) + .Returns(new List() { - new ContentType(-1) {Alias = "Folder", Name = "Folder", Id = 1031, Icon = "icon-folder"}, - new ContentType(-1) {Alias = "Image", Name = "Image", Id = 1032, Icon = "icon-picture"} + new MediaType(-1) {Alias = "Folder", Name = "Folder", Id = 1031, Icon = "icon-folder"}, + new MediaType(-1) {Alias = "Image", Name = "Image", Id = 1032, Icon = "icon-picture"} }); contentTypeService = contentTypeServiceMock.Object; } From df12f19c53b7e4bf0ae1df89e212579a4bfdde24 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 14 Nov 2016 16:08:21 +0100 Subject: [PATCH 71/88] U4-9115 - fix issue --- .../Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs index d5c377fba1..c56ae0e2f1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/AddLockObjects.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFiv // be safe: delete old umbracoNode lock objects if any db.Execute("DELETE FROM umbracoNode WHERE id=@id;", new { id }); // then create umbracoLock object - db.Execute("INSERT umbracoLock (id, name, value) VALUES (@id, '@name', 1);", new { id, name }); + db.Execute("INSERT umbracoLock (id, name, value) VALUES (@id, @name, 1);", new { id, name }); return string.Empty; }); } From 740abb14265541891bf7f89f629b1c5cb7199821 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 14 Nov 2016 16:17:58 +0100 Subject: [PATCH 72/88] We don't support C# 6 yet --- src/Umbraco.Tests/Persistence/LocksTests.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests/Persistence/LocksTests.cs index e973b14614..624a94f39f 100644 --- a/src/Umbraco.Tests/Persistence/LocksTests.cs +++ b/src/Umbraco.Tests/Persistence/LocksTests.cs @@ -254,7 +254,9 @@ namespace Umbraco.Tests.Persistence var info = database.Query("SELECT * FROM sys.lock_information;"); Console.WriteLine("LOCKS:"); foreach (var row in info) - Console.WriteLine($"> {row.request_spid} {row.resource_type} {row.resource_description} {row.request_mode} {row.resource_table} {row.resource_table_id} {row.request_status}"); + Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", row.request_spid, + row.resource_type, row.resource_description, row.request_mode, row.resource_table, + row.resource_table_id, row.request_status)); Thread.Sleep(6000); } catch (Exception e) @@ -277,7 +279,9 @@ namespace Umbraco.Tests.Persistence var info = database.Query("SELECT * FROM sys.lock_information;"); Console.WriteLine("LOCKS:"); foreach (var row in info) - Console.WriteLine($"> {row.request_spid} {row.resource_type} {row.resource_description} {row.request_mode} {row.resource_table} {row.resource_table_id} {row.request_status}"); + Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", row.request_spid, + row.resource_type, row.resource_description, row.request_mode, row.resource_table, + row.resource_table_id, row.request_status)); Thread.Sleep(1000); } catch (Exception e) From 5b714572e3d2d80a087ed90ba185465e46971d17 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 14 Nov 2016 17:04:40 +0100 Subject: [PATCH 73/88] Bump version --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 469bf7a466..df03fe8381 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,2 +1,2 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.5.4 \ No newline at end of file +7.5.5 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 39cbd18c2a..b3b5633864 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.5.4")] -[assembly: AssemblyInformationalVersion("7.5.4")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.5.5")] +[assembly: AssemblyInformationalVersion("7.5.5")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index ddde8fbd5f..ccf4aecf13 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.5.4"); + private static readonly Version Version = new Version("7.5.5"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 6f2728a241..a31d174e14 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2412,9 +2412,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" True True - 7540 + 7550 / - http://localhost:7540 + http://localhost:7550 False False From 2de465e8f397f4cd87550984ea2648e4477e0d8c Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 14 Nov 2016 17:39:28 +0100 Subject: [PATCH 74/88] Ensures that indexes are rebuild for items that have had their content type's or property type's aliases changed, also ensures that the content refresher kicks in if a property type alias has changed (this wasn't previously being done) --- src/Umbraco.Core/Models/ContentTypeBase.cs | 2 +- .../Cache/ContentTypeCacheRefresher.cs | 73 +++++++++-------- src/Umbraco.Web/Search/ExamineEvents.cs | 80 ++++++++++++++++++- 3 files changed, 114 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 2982713e5a..88476f946d 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -326,7 +326,7 @@ namespace Umbraco.Core.Models } } - /// + /// /// A boolean flag indicating if a property type has been removed from this instance. /// /// diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 44a6efe9ff..246571d479 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Cache /// /// /// - private static JsonPayload[] DeserializeFromJsonPayload(string json) + internal static JsonPayload[] DeserializeFromJsonPayload(string json) { var serializer = new JavaScriptSerializer(); var jsonObject = serializer.Deserialize(json); @@ -45,30 +45,28 @@ namespace Umbraco.Web.Cache /// /// if the item was deleted /// - private static JsonPayload FromContentType(IContentTypeBase contentType, bool isDeleted = false) + internal static JsonPayload FromContentType(IContentTypeBase contentType, bool isDeleted = false) { var payload = new JsonPayload - { - Alias = contentType.Alias, - Id = contentType.Id, - PropertyTypeIds = contentType.PropertyTypes.Select(x => x.Id).ToArray(), - //either IContentType or IMediaType or IMemberType - Type = (contentType is IContentType) - ? typeof(IContentType).Name - : (contentType is IMediaType) + { + Alias = contentType.Alias, + Id = contentType.Id, + PropertyTypeIds = contentType.PropertyTypes.Select(x => x.Id).ToArray(), + //either IContentType or IMediaType or IMemberType + Type = (contentType is IContentType) + ? typeof(IContentType).Name + : (contentType is IMediaType) ? typeof(IMediaType).Name : typeof(IMemberType).Name, - DescendantPayloads = contentType.Descendants().Select(x => FromContentType(x)).ToArray(), - WasDeleted = isDeleted - }; - //here we need to check if the alias of the content type changed or if one of the properties was removed. - var dirty = contentType as IRememberBeingDirty; - if (dirty != null) - { - payload.PropertyRemoved = dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved"); - payload.AliasChanged = dirty.WasPropertyDirty("Alias"); - payload.IsNew = dirty.WasPropertyDirty("HasIdentity"); - } + DescendantPayloads = contentType.Descendants().Select(x => FromContentType(x)).ToArray(), + WasDeleted = isDeleted, + PropertyRemoved = contentType.WasPropertyDirty("HasPropertyTypeBeenRemoved"), + AliasChanged = contentType.WasPropertyDirty("Alias"), + PropertyTypeAliasChanged = contentType.PropertyTypes.Any(x => x.WasPropertyDirty("Alias")), + IsNew = contentType.WasPropertyDirty("HasIdentity") + }; + + return payload; } @@ -90,7 +88,7 @@ namespace Umbraco.Web.Cache #region Sub classes - private class JsonPayload + internal class JsonPayload { public JsonPayload() { @@ -103,6 +101,7 @@ namespace Umbraco.Web.Cache public string Type { get; set; } public bool AliasChanged { get; set; } public bool PropertyRemoved { get; set; } + public bool PropertyTypeAliasChanged { get; set; } public JsonPayload[] DescendantPayloads { get; set; } public bool WasDeleted { get; set; } public bool IsNew { get; set; } @@ -190,21 +189,21 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.IdToKeyCacheKey); ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey); - payloads.ForEach(payload => + foreach (var payload in payloads) + { + //clear the cache for each item + ClearContentTypeCache(payload); + + //we only need to do this for IContentType NOT for IMediaType, we don't want to refresh the whole cache. + //if the item was deleted or the alias changed or property removed then we need to refresh the content. + //and, don't refresh the cache if it is new. + if (payload.Type == typeof(IContentType).Name + && payload.IsNew == false + && (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved || payload.PropertyTypeAliasChanged)) { - //clear the cache for each item - ClearContentTypeCache(payload); - - //we only need to do this for IContentType NOT for IMediaType, we don't want to refresh the whole cache. - //if the item was deleted or the alias changed or property removed then we need to refresh the content. - //and, don't refresh the cache if it is new. - if (payload.Type == typeof(IContentType).Name - && !payload.IsNew - && (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved)) - { - needsContentRefresh = true; - } - }); + needsContentRefresh = true; + } + } //need to refresh the xml content cache if required if (needsContentRefresh) @@ -237,7 +236,7 @@ namespace Umbraco.Web.Cache //cache if only a media type has changed. //we don't want to update the routes cache if all of the content types here are new. if (payloads.Any(x => x.Type == typeof(IContentType).Name) - && !payloads.All(x => x.IsNew)) //if they are all new then don't proceed + && payloads.All(x => x.IsNew) == false) //if they are all new then don't proceed { // SD: we need to clear the routes cache here! // diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index 20701fe330..d02850bffe 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml; @@ -67,12 +68,12 @@ namespace Umbraco.Web.Search /// /// This is used to refresh content indexers IndexData based on the DataService whenever a content type is changed since - /// properties may have been added/removed + /// properties may have been added/removed, then we need to re-index any required data if aliases have been changed /// /// /// /// - /// See: http://issues.umbraco.org/issue/U4-4798 + /// See: http://issues.umbraco.org/issue/U4-4798, http://issues.umbraco.org/issue/U4-7833 /// static void ContentTypeCacheRefresherCacheUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs e) { @@ -81,6 +82,79 @@ namespace Umbraco.Web.Search { provider.RefreshIndexerDataFromDataService(); } + + if (e.MessageType == MessageType.RefreshByJson) + { + var contentTypesChanged = new HashSet(); + var mediaTypesChanged = new HashSet(); + var memberTypesChanged = new HashSet(); + + var payloads = ContentTypeCacheRefresher.DeserializeFromJsonPayload(e.MessageObject.ToString()); + foreach (var payload in payloads) + { + if (payload.IsNew == false + && (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved || payload.PropertyTypeAliasChanged)) + { + //if we get here it means that some aliases have changed and the indexes for those particular doc types will need to be updated + if (payload.Type == typeof(IContentType).Name) + { + //if it is content + contentTypesChanged.Add(payload.Alias); + } + else if (payload.Type == typeof(IMediaType).Name) + { + //if it is media + mediaTypesChanged.Add(payload.Alias); + } + else if (payload.Type == typeof(IMemberType).Name) + { + //if it is members + memberTypesChanged.Add(payload.Alias); + } + } + } + + //TODO: We need to update Examine to support re-indexing multiple items at once instead of one by one which will speed up + // the re-indexing process, we don't want to revert to rebuilding the whole thing! + + if (contentTypesChanged.Count > 0) + { + foreach (var alias in contentTypesChanged) + { + var ctType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias); + var contentItems = ApplicationContext.Current.Services.ContentService.GetContentOfContentType(ctType.Id); + foreach (var contentItem in contentItems) + { + ReIndexForContent(contentItem, contentItem.HasPublishedVersion && contentItem.Trashed == false); + } + } + } + if (mediaTypesChanged.Count > 0) + { + foreach (var alias in mediaTypesChanged) + { + var ctType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias); + var mediaItems = ApplicationContext.Current.Services.MediaService.GetMediaOfMediaType(ctType.Id); + foreach (var mediaItem in mediaItems) + { + ReIndexForMedia(mediaItem, mediaItem.Trashed == false); + } + } + } + if (memberTypesChanged.Count > 0) + { + foreach (var alias in memberTypesChanged) + { + var ctType = ApplicationContext.Current.Services.MemberTypeService.Get(alias); + var memberItems = ApplicationContext.Current.Services.MemberService.GetMembersByMemberType(ctType.Id); + foreach (var memberItem in memberItems) + { + ReIndexForMember(memberItem); + } + } + } + } + } static void MemberCacheRefresherCacheUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs e) @@ -432,7 +506,7 @@ namespace Umbraco.Web.Search //add an icon attribute to get indexed xml.Add(new XAttribute("icon", sender.ContentType.Icon)); - ExamineManager.Instance.ReIndexNode( + ExamineManager.Instance.ReIndexNode( xml, IndexTypes.Content, ExamineManager.Instance.IndexProviderCollection.OfType() From 213f02cbc6b7f30f94ab12d3e122e0f28947b537 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 14 Nov 2016 17:39:46 +0100 Subject: [PATCH 75/88] Revert "U4-7833 Changing doc type results in invalid cache, lucene indexes, etc..." This reverts commit 3deda7efabf436d905cad69b6d7a7c164bd31de9. --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 3 +-- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 -- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 1 - src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx | 4 +--- src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs | 3 --- .../umbraco/dialogs/ChangeDocType.aspx.designer.cs | 9 --------- 6 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index b9996db11c..b0452fb631 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -109,10 +109,9 @@ Ny skabelon Ny type ingen - Du bør genopbygge dine Examine indekser via 'Examine Management' dashboarded i udvikler sektionen for at sikre at denne ændring registreres i hele dit website. Indhold Vælg ny dokumenttype - Dokumenttypen på det valgte indhold blev skiftet til [new type], og følgende egenskaber blev overført: + Dokumenttypen på detvalgte indhold blev skiftet til [new type], og følgende egenskaber blev overført: til Overførsel af egenskaber kunne ikke fuldføres, da en eller flere egenskaber er indstillet til at blive overført mere end én gang. Kun andre dokumenttyper, der er gyldige på denne placering, vises. diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 317c044f9d..bdc2647563 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -100,7 +100,6 @@ Show styles Insert table Generate models - Save and generate models To change the document type for the selected content, first select from the list of valid types for this location. @@ -115,7 +114,6 @@ New Template New Type none - Please make sure to rebuild your Examine indexes using the 'Examine Management' dashboard in the Developer section, to ensure this change is applied everywhere. Content Select New Document Type The document type of the selected content has been successfully changed to [new type] and the following properties mapped: diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 7a39c59406..7544cacc0c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -115,7 +115,6 @@ New Template New Type none - Please make sure to rebuild your Examine indexes using the 'Examine Management' dashboard in the Developer section, to ensure this change is applied everywhere. Content Select New Document Type The document type of the selected content has been successfully changed to [new type] and the following properties mapped: diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx index a11c5cab49..77deab2709 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx @@ -97,9 +97,7 @@

    - -

    - <%=umbraco.ui.Text("defaultdialogs", "closeThisWindow") %> + <%=umbraco.ui.Text("defaultdialogs", "closeThisWindow") %>

    diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs index 5d9243f994..0db3b6c36e 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs @@ -265,13 +265,10 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs { ContentPublishedMessage.Text = global::umbraco.ui.Text("changeDocType", "contentRepublished"); ContentPublishedMessage.Visible = true; - RebuildIndexesMessage.Text = global::umbraco.ui.Text("changeDocType", "rebuildIndexes"); - RebuildIndexesMessage.Visible = true; } else { ContentPublishedMessage.Visible = false; - RebuildIndexesMessage.Visible = false; } SuccessPlaceholder.Visible = true; SaveAndCancelPlaceholder.Visible = false; diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.designer.cs b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.designer.cs index 5fa673836a..b774a3ff79 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.designer.cs +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.designer.cs @@ -165,15 +165,6 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs { /// protected global::System.Web.UI.WebControls.Literal ContentPublishedMessage; - /// - /// RebuildIndexesMessage control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal RebuildIndexesMessage; - /// /// ValidationPlaceholder control. /// From f5fe07ffb7d924e28975452cbf5cecb9db93bab4 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 14 Nov 2016 16:05:24 +0100 Subject: [PATCH 76/88] U4-6994 - more N+1 and refactoring --- .../Repositories/ContentRepository.cs | 210 ++++++------------ .../Repositories/MediaRepository.cs | 136 ++++++------ .../Repositories/MemberRepository.cs | 107 +++++---- 3 files changed, 201 insertions(+), 252 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 5d8818e1ac..de8d1bdd8b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -1,22 +1,14 @@ using System; using System.Collections.Generic; -using System.Data; using System.Globalization; using System.Linq; -using System.Linq.Expressions; -using System.Net.Http.Headers; -using System.Text; using System.Xml; using System.Xml.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dynamics; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; - using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -81,7 +73,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); if (ids.Any()) { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + sql.Where("umbracoNode.id in (@ids)", new { ids }); } //we only want the newest ones with this method @@ -228,7 +220,7 @@ namespace Umbraco.Core.Persistence.Repositories private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction tr, int pageSize) { var pageIndex = 0; - var total = long.MinValue; + long total; var processed = 0; do { @@ -239,7 +231,7 @@ namespace Umbraco.Core.Persistence.Repositories // see: http://issues.umbraco.org/issue/U4-6322 & http://issues.umbraco.org/issue/U4-5982 var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, new Tuple("cmsDocument", "nodeId"), - ProcessQuery, "Path", Direction.Ascending, true); + sql => ProcessQuery(sql), "Path", Direction.Ascending, true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) @@ -259,7 +251,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) .OrderByDescending(x => x.VersionDate, SqlSyntax); - return GetAllBySql(sql); + return ProcessQuery(sql, true); } public override IContent GetByVersion(Guid versionId) @@ -670,63 +662,7 @@ namespace Umbraco.Core.Persistence.Repositories .OrderBy(x => x.Level, SqlSyntax) .OrderBy(x => x.SortOrder, SqlSyntax); - return GetAllBySql(sql); - } - - private IEnumerable GetAllBySql(Sql sql) - { - var dtos = Database.Fetch(sql); - var content = new IContent[dtos.Count]; - var defs = new List(); - - for (var i = 0; i < dtos.Count; i++) - { - var dto = dtos[i]; - - // if the cache contains the published version, use it - var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - if (cached != null && cached.Published) - { - content[i] = cached; - continue; - } - - // else, need to fetch the version from the database - var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - var c = factory.BuildEntity(dto); - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - c.Template = _templateRepository.Get(dto.TemplateId.Value); - content[i] = c; - - defs.Add(new DocumentDefinition( - dto.NodeId, - dto.VersionId, - dto.ContentVersionDto.VersionDate, - dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - contentType - )); - } - - // going to load all properties for all docs from database, - // but at least in one queries thus avoiding N+1 - var propertyData = GetPropertyCollection(sql, defs); - - var dtoIndex = 0; - var defIndex = 0; - while (true) - { - if (defIndex == defs.Count) return content; - while (dtoIndex < dtos.Count && dtos[dtoIndex].NodeId != defs[defIndex].Id) dtoIndex++; - var cc = content[dtoIndex]; - cc.Properties = propertyData[cc.Id]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)cc).ResetDirtyProperties(false); - - defIndex++; - } + return ProcessQuery(sql, true); } /// @@ -902,7 +838,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsDocument", "nodeId"), - ProcessQuery, orderBy, orderDirection, orderBySystemField, + sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -933,83 +869,79 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; return base.GetDatabaseFieldNameForOrderBy(orderBy); } - private IEnumerable ProcessQuery(Sql sql) + private IEnumerable ProcessQuery(Sql sql, bool withCache = false) { - //NOTE: This doesn't allow properties to be part of the query + // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sql); + if (dtos.Count == 0) return Enumerable.Empty(); - //nothing found - if (dtos.Any() == false) return Enumerable.Empty(); + var content = new IContent[dtos.Count]; + var defs = new List(); + var templateIds = new List(); - //content types - //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types - var contentTypes = _contentTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()) - .ToArray(); - - - var ids = dtos - .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - .Select(x => x.TemplateId.Value).ToArray(); - - //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types - var templates = ids.Length == 0 ? Enumerable.Empty() : _templateRepository.GetAll(ids).ToArray(); - - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); - - //Go get the property data for each document - var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.VersionId, - d.dto.ContentVersionDto.VersionDate, - d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - d.contentType)); - - var propertyData = GetPropertyCollection(sql, docDefs); - - return dtosWithContentTypes.Select(d => CreateContentFromDto( - d.dto, - contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), - templates.FirstOrDefault(tem => tem.Id == (d.dto.TemplateId.HasValue ? d.dto.TemplateId.Value : -1)), - propertyData[d.dto.NodeId])); - } - - /// - /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. - /// - /// - /// - /// - /// - /// - private IContent CreateContentFromDto(DocumentDto dto, - IContentType contentType, - ITemplate template, - Models.PropertyCollection propCollection) - { - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - var content = factory.BuildEntity(dto); - - //Check if template id is set on DocumentDto, and get ITemplate if it is. - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + for (var i = 0; i < dtos.Count; i++) { - content.Template = template ?? _templateRepository.Get(dto.TemplateId.Value); - } - else - { - //ensure there isn't one set. - content.Template = null; + var dto = dtos[i]; + + // if the cache contains the published version, use it + if (withCache) + { + var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + if (cached != null && cached.Published) + { + content[i] = cached; + continue; + } + } + + // else, need to fetch from the database + // content type repository is full-cache so OK to get each one independently + var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); + content[i] = factory.BuildEntity(dto); + + // need template + if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + templateIds.Add(dto.TemplateId.Value); + + // need properties + defs.Add(new DocumentDefinition( + dto.NodeId, + dto.VersionId, + dto.ContentVersionDto.VersionDate, + dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + contentType + )); } - content.Properties = propCollection; + // load all required templates in 1 query + var templates = _templateRepository.GetAll(templateIds.ToArray()) + .ToDictionary(x => x.Id, x => x); + + // load all properties for all documents from database in 1 query + var propertyData = GetPropertyCollection(sql, defs); + + // assign + var dtoIndex = 0; + foreach (var def in defs) + { + // move to corresponding item (which has to exist) + while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++; + + // complete the item + var cc = content[dtoIndex]; + var dto = dtos[dtoIndex]; + ITemplate template = null; + if (dto.TemplateId.HasValue) + templates.TryGetValue(dto.TemplateId.Value, out template); // else null + cc.Template = template; + cc.Properties = propertyData[cc.Id]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity) cc).ResetDirtyProperties(false); + } - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)content).ResetDirtyProperties(false); return content; } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 598c9e912d..48372e5517 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -4,21 +4,17 @@ using System.Globalization; using System.Linq; using System.Text; using System.Xml.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Dynamics; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; - +using Umbraco.Core.Cache; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -137,6 +133,74 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of VersionableRepositoryBase + public override IEnumerable GetAllVersions(int id) + { + var sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { Id = id }) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + return ProcessQuery(sql, true); + } + + private IEnumerable ProcessQuery(Sql sql, bool withCache = false) + { + // fetch returns a list so it's ok to iterate it in this method + var dtos = Database.Fetch(sql); + var content = new IMedia[dtos.Count]; + var defs = new List(); + + for (var i = 0; i < dtos.Count; i++) + { + var dto = dtos[i]; + + // if the cache contains the item, use it + if (withCache) + { + var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + if (cached != null) + { + content[i] = cached; + continue; + } + } + + // else, need to fetch from the database + // content type repository is full-cache so OK to get each one independently + var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); + content[i] = factory.BuildEntity(dto); + + // need properties + defs.Add(new DocumentDefinition( + dto.NodeId, + dto.VersionId, + dto.VersionDate, + dto.ContentDto.NodeDto.CreateDate, + contentType + )); + } + + // load all properties for all documents from database in 1 query + var propertyData = GetPropertyCollection(sql, defs); + + // assign + var dtoIndex = 0; + foreach (var def in defs) + { + // move to corresponding item (which has to exist) + while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++; + + // complete the item + var cc = content[dtoIndex]; + cc.Properties = propertyData[cc.Id]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity) cc).ResetDirtyProperties(false); + } + + return content; + } + public override IMedia GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); @@ -175,7 +239,7 @@ namespace Umbraco.Core.Persistence.Repositories var subQuery = new Sql() .Select("id") .From(SqlSyntax) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + .Where(x => x.NodeObjectType == NodeObjectTypeId); var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); Database.Execute(deleteSql); @@ -191,7 +255,7 @@ namespace Umbraco.Core.Persistence.Repositories .Where(x => x.NodeObjectType == NodeObjectTypeId); var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - Database.Execute(deleteSql); + Database.Execute(deleteSql); } //now insert the data, again if something fails here, the whole transaction is reversed @@ -460,67 +524,11 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsContentVersion", "contentId"), - ProcessQuery, orderBy, orderDirection, orderBySystemField, + sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, filterCallback); } - private IEnumerable ProcessQuery(Sql sql) - { - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray(); - - //content types - var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _mediaTypeRepository.GetAll(ids).ToArray(); - - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); - - //Go get the property data for each document - var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.VersionId, - d.dto.VersionDate, - d.dto.ContentDto.NodeDto.CreateDate, - d.contentType)) - .ToArray(); - - var propertyData = GetPropertyCollection(sql, docDefs); - - return dtosWithContentTypes.Select(d => CreateMediaFromDto( - d.dto, - contentTypes.First(ct => ct.Id == d.dto.ContentDto.ContentTypeId), - propertyData[d.dto.NodeId])); - } - - /// - /// Private method to create a media object from a ContentDto - /// - /// - /// - /// - /// - private IMedia CreateMediaFromDto(ContentVersionDto dto, - IMediaType contentType, - PropertyCollection propCollection) - { - var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - media.Properties = propCollection; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - return media; - } - /// /// Private method to create a media object from a ContentDto /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 216fc223db..4d8b19e522 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -2,24 +2,19 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Linq.Expressions; using System.Text; using System.Xml.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; - +using Umbraco.Core.Cache; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Dynamics; namespace Umbraco.Core.Persistence.Repositories { @@ -380,6 +375,14 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of VersionableRepositoryBase + public override IEnumerable GetAllVersions(int id) + { + var sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { Id = id }) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + return ProcessQuery(sql, true); + } + public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null) { @@ -645,7 +648,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsMember", "nodeId"), - ProcessQuery, orderBy, orderDirection, orderBySystemField, + sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -685,59 +688,65 @@ namespace Umbraco.Core.Persistence.Repositories return base.GetEntityPropertyNameForOrderBy(orderBy); } - private IEnumerable ProcessQuery(Sql sql) + private IEnumerable ProcessQuery(Sql sql, bool withCache = false) { - //NOTE: This doesn't allow properties to be part of the query + // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sql); - var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray(); + var content = new IMember[dtos.Count]; + var defs = new List(); - //content types - var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _memberTypeRepository.GetAll(ids).ToArray(); + for (var i = 0; i < dtos.Count; i++) + { + var dto = dtos[i]; - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); + // if the cache contains the item, use it + if (withCache) + { + var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + if (cached != null) + { + content[i] = cached; + continue; + } + } - //Go get the property data for each document - IEnumerable docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.ContentVersionDto.VersionId, - d.dto.ContentVersionDto.VersionDate, - d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - d.contentType)); + // else, need to fetch from the database + // content type repository is full-cache so OK to get each one independently + var contentType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + var factory = new MemberFactory(contentType, NodeObjectTypeId, dto.NodeId); + content[i] = factory.BuildEntity(dto); - var propertyData = GetPropertyCollection(sql, docDefs); + // need properties + defs.Add(new DocumentDefinition( + dto.NodeId, + dto.ContentVersionDto.VersionId, + dto.ContentVersionDto.VersionDate, + dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + contentType + )); + } - return dtosWithContentTypes.Select(d => CreateMemberFromDto( - d.dto, - contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), - propertyData[d.dto.NodeId])); - } + // load all properties for all documents from database in 1 query + var propertyData = GetPropertyCollection(sql, defs); - /// - /// Private method to create a member object from a MemberDto - /// - /// - /// - /// - /// - private IMember CreateMemberFromDto(MemberDto dto, - IMemberType contentType, - PropertyCollection propCollection) - { - var factory = new MemberFactory(contentType, NodeObjectTypeId, dto.ContentVersionDto.NodeId); - var member = factory.BuildEntity(dto); + // assign + var dtoIndex = 0; + foreach (var def in defs) + { + // move to corresponding item (which has to exist) + while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++; - member.Properties = propCollection; + // complete the item + var cc = content[dtoIndex]; + cc.Properties = propertyData[cc.Id]; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)member).ResetDirtyProperties(false); - return member; + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)cc).ResetDirtyProperties(false); + } + + return content; } /// From cfc1704c64537d30ed9de512f3c09e45ffeb251e Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 14 Nov 2016 18:33:16 +0100 Subject: [PATCH 77/88] Fix build after merge --- src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 42f3594478..b735954b6d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -209,6 +209,7 @@ namespace Umbraco.Core.Persistence.Repositories } baseId = xmlItems.Last().NodeId; } + } public override IEnumerable GetAllVersions(int id) { From 9d2fb0dd03317332c7282f74762ee9da11b29d5b Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 14 Nov 2016 19:01:44 +0100 Subject: [PATCH 78/88] Fix after merges --- src/Umbraco.Core/Services/MediaService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 8bb22bc77f..0e3078979d 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading; +using System.Xml.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.IO; From 6f39439e3dffd915e885f6186f19b8e3d3436fdf Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 15 Nov 2016 12:10:36 +0100 Subject: [PATCH 79/88] removing string.empty checks for attributes. removing the redundant clean for outputting model.value in TextString.cshtml. --- .../Views/Partials/Grid/Bootstrap2-Fluid.cshtml | 5 +---- src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml | 5 +---- .../Views/Partials/Grid/Bootstrap3-Fluid.cshtml | 5 +---- src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml | 5 +---- .../Views/Partials/Grid/Editors/TextString.cshtml | 2 +- 5 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml index 65b9b8abc7..f6b93139ce 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml @@ -67,10 +67,7 @@ foreach (JProperty property in cfg.Properties()) { var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); - if (string.IsNullOrWhiteSpace(propertyValue) == false) - { - attrs.Add(property.Name + "='" + propertyValue + "'"); - } + attrs.Add(property.Name + "=\"" + propertyValue + "\""); } JObject style = contentItem.styles; diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml index 37e3c84dad..c5fabe2abf 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml @@ -67,10 +67,7 @@ foreach (JProperty property in cfg.Properties()) { var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); - if (string.IsNullOrWhiteSpace(propertyValue) == false) - { - attrs.Add(property.Name + "=\"" + propertyValue + "\""); - } + attrs.Add(property.Name + "=\"" + propertyValue + "\""); } JObject style = contentItem.styles; diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml index 45be239245..b7e8ef34fb 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml @@ -63,10 +63,7 @@ foreach (JProperty property in cfg.Properties()) { var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); - if (string.IsNullOrWhiteSpace(propertyValue) == false) - { - attrs.Add(property.Name + "='" + propertyValue + "'"); - } + attrs.Add(property.Name + "=\"" + propertyValue + "\""); } JObject style = contentItem.styles; diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml index afadd3d93e..3a4fa3b8e2 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml @@ -67,10 +67,7 @@ foreach (JProperty property in cfg.Properties()) { var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); - if (string.IsNullOrWhiteSpace(propertyValue) == false) - { - attrs.Add(property.Name + "=\"" + propertyValue +"\""); - } + attrs.Add(property.Name + "=\"" + propertyValue + "\""); } JObject style = contentItem.styles; diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml index 4a15201997..5a570efdb5 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml @@ -16,6 +16,6 @@ else { -
    @TemplateUtilities.CleanForXss(Model.value.ToString())
    +
    @Model.value
    } From e499f2d1489a76d57a3896afe5050d9a9bc25e58 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 15 Nov 2016 13:44:44 +0100 Subject: [PATCH 80/88] U4-9108 - add missing change --- src/Umbraco.Core/Sync/ApplicationUrlHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index a417ec601a..7b713374aa 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -114,7 +114,8 @@ namespace Umbraco.Core.Sync // - contain a scheme // - end or not with a slash, it will be taken care of // eg "http://www.mysite.com/umbraco" - var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2; + var resolver = ServerRegistrarResolver.HasCurrent ? ServerRegistrarResolver.Current : null; + var registrar = resolver == null ? null : resolver.Registrar as IServerRegistrar2; url = registrar == null ? null : registrar.GetCurrentServerUmbracoApplicationUrl(); if (url.IsNullOrWhiteSpace() == false) { From 6c8a033a567271df1f7f60e2601112c6d1e8496f Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 15 Nov 2016 13:46:49 +0100 Subject: [PATCH 81/88] Fix build.bat --- build/Build.bat | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/build/Build.bat b/build/Build.bat index 04f13b1085..d20eef70fb 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -2,7 +2,6 @@ :: UMBRACO BUILD FILE -ECHO. :: ensure we have UmbracoVersion.txt IF NOT EXIST UmbracoVersion.txt ( @@ -19,7 +18,7 @@ FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED COMMENT SE REM process args SET INTEGRATION=0 -SET nuGetFolder=%CD%\..\src\packages\ +SET nuGetFolder=%CD%\..\src\packages SET SKIPNUGET=0 :processArgs @@ -82,9 +81,9 @@ REM run SET VERSION=%RELEASE% IF [%COMMENT%] EQU [] (SET VERSION=%RELEASE%) ELSE (SET VERSION=%RELEASE%-%COMMENT%) -ECHO. +ECHO ################################################################ ECHO Building Umbraco %VERSION% -ECHO. +ECHO ################################################################ SET MSBUILDPATH=C:\Program Files (x86)\MSBuild\14.0\Bin SET MSBUILD="%MSBUILDPATH%\MsBuild.exe" @@ -100,10 +99,10 @@ RD ..\src\Umbraco.Web.UI.Client\bower_components /Q /S ECHO. ECHO Removing existing built files to make sure everything is clean as a whistle RMDIR /Q /S _BuildOutput -DEL /F /Q UmbracoCms.*.zip -DEL /F /Q UmbracoExamine.*.zip -DEL /F /Q UmbracoCms.*.nupkg -DEL /F /Q webpihash.txt +DEL /F /Q UmbracoCms.*.zip 2>NUL +DEL /F /Q UmbracoExamine.*.zip 2>NUL +DEL /F /Q UmbracoCms.*.nupkg 2>NUL +DEL /F /Q webpihash.txt 2>NUL ECHO. ECHO Making sure Git is in the path so that the build can succeed @@ -116,16 +115,16 @@ SET PATH="C:\Program Files (x86)\Git\cmd";"C:\Program Files\Git\cmd";%PATH% ECHO. ECHO Making sure we have a web.config -IF NOT EXIST %CD%\..\src\Umbraco.Web.UI\web.config COPY %CD%\..\src\Umbraco.Web.UI\web.Template.config %CD%\..\src\Umbraco.Web.UI\web.config +IF NOT EXIST "%CD%\..\src\Umbraco.Web.UI\web.config" COPY "%CD%\..\src\Umbraco.Web.UI\web.Template.config" "%CD%\..\src\Umbraco.Web.UI\web.config" ECHO. ECHO Reporting NuGet version -%CD%\..\src\.nuget\NuGet.exe help | findstr "^NuGet Version:" +"%CD%\..\src\.nuget\NuGet.exe" help | findstr "^NuGet Version:" ECHO. ECHO Restoring NuGet packages ECHO Into %nuGetFolder% -%CD%\..\src\.nuget\NuGet.exe restore %CD%\..\src\umbraco.sln -Verbosity Quiet -NonInteractive -PackagesDirectory %nuGetFolder% +"%CD%\..\src\.nuget\NuGet.exe" restore "%CD%\..\src\umbraco.sln" -Verbosity Quiet -NonInteractive -PackagesDirectory "%nuGetFolder%" IF ERRORLEVEL 1 GOTO :error ECHO. @@ -135,7 +134,7 @@ ECHO This takes a few minutes and logging is set to report warnings ECHO and errors only so it might seems like nothing is happening for a while. ECHO You can check the msbuild.log file for progress. ECHO. -%MSBUILD% "Build.proj" /p:BUILD_RELEASE=%RELEASE% /p:BUILD_COMMENT=%COMMENT% /p:NugetPackagesDirectory=%nuGetFolder% /consoleloggerparameters:Summary;ErrorsOnly;WarningsOnly /fileLogger +%MSBUILD% "Build.proj" /p:BUILD_RELEASE=%RELEASE% /p:BUILD_COMMENT=%COMMENT% /p:NugetPackagesDirectory="%nuGetFolder%" /consoleloggerparameters:Summary;ErrorsOnly /fileLogger IF ERRORLEVEL 1 GOTO error ECHO. From 0478f01113bd8313aead9d022a1895da7133c8c8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 15 Nov 2016 14:08:30 +0100 Subject: [PATCH 82/88] Fix merge --- src/Umbraco.Core/Umbraco.Core.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 25112f2389..9f94796b92 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -427,7 +427,6 @@ - From 289d7a01d8ced0a360834244129b68aca2ba7dee Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 15 Nov 2016 18:41:03 +0100 Subject: [PATCH 83/88] Fix BulkInsert, was rolling back --- .../Persistence/PetaPocoExtensions.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index d1667456e6..22e66935bf 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -60,7 +60,7 @@ namespace Umbraco.Core.Persistence /// Safely inserts a record, or updates if it exists, based on a unique constraint. ///
    /// - /// + /// /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object /// passed in will contain the updated value. /// @@ -82,7 +82,7 @@ namespace Umbraco.Core.Persistence ///
    /// /// - /// + /// /// If the entity has a composite key they you need to specify the update command explicitly /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object /// passed in will contain the updated value. @@ -134,7 +134,7 @@ namespace Umbraco.Core.Persistence if (rowCount > 0) return RecordPersistenceType.Update; - // failed: does not exist (due to race cond RC2), need to insert + // failed: does not exist (due to race cond RC2), need to insert // loop } } @@ -191,7 +191,7 @@ namespace Umbraco.Core.Persistence using (var tr = db.GetTransaction()) { - db.BulkInsertRecords(collection, tr, SqlSyntaxContext.SqlSyntaxProvider, true); + db.BulkInsertRecords(collection, tr, SqlSyntaxContext.SqlSyntaxProvider, true, true); // use native, commit } } @@ -202,7 +202,7 @@ namespace Umbraco.Core.Persistence /// /// /// - /// + /// /// /// /// If this is false this will try to just generate bulk insert statements instead of using the current SQL platform's bulk @@ -229,7 +229,7 @@ namespace Umbraco.Core.Persistence try { - int processed = 0; + int processed = 0; var usedNativeSqlPlatformInserts = useNativeSqlPlatformBulkInsert && NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed); @@ -243,7 +243,7 @@ namespace Umbraco.Core.Persistence { //SqlCe doesn't support bulk insert statements! foreach (var poco in collection) - { + { db.Insert(poco); } } @@ -307,9 +307,9 @@ namespace Umbraco.Core.Persistence /// /// Sql commands with populated command parameters required to execute the sql statement /// - /// The limits for number of parameters are 2100 (in sql server, I think there's many more allowed in mysql). So - /// we need to detect that many params and split somehow. - /// For some reason the 2100 limit is not actually allowed even though the exception from sql server mentions 2100 as a max, perhaps it is 2099 + /// The limits for number of parameters are 2100 (in sql server, I think there's many more allowed in mysql). So + /// we need to detect that many params and split somehow. + /// For some reason the 2100 limit is not actually allowed even though the exception from sql server mentions 2100 as a max, perhaps it is 2099 /// that is max. I've reduced it to 2000 anyways. /// internal static IDbCommand[] GenerateBulkInsertCommand( @@ -332,7 +332,7 @@ namespace Umbraco.Core.Persistence var paramsPerItem = pd.Columns.Count(i => IncludeColumn(pd, i)); //Example calc: - // Given: we have 4168 items in the itemArray, each item contains 8 command parameters (values to be inserterted) + // Given: we have 4168 items in the itemArray, each item contains 8 command parameters (values to be inserterted) // 2100 / 8 = 262.5 // Math.Floor(2100 / 8) = 262 items per trans // 4168 / 262 = 15.908... = there will be 16 trans in total @@ -409,15 +409,15 @@ namespace Umbraco.Core.Persistence dbConnection = profiledConnection.InnerConnection; } - //check if it's SQL or SqlCe - + //check if it's SQL or SqlCe + var sqlConnection = dbConnection as SqlConnection; if (sqlConnection != null) { processed = BulkInsertRecordsSqlServer(db, (SqlServerSyntaxProvider)syntaxProvider, pd, collection); return true; } - + var sqlCeConnection = dbConnection as SqlCeConnection; if (sqlCeConnection != null) { @@ -442,7 +442,7 @@ namespace Umbraco.Core.Persistence internal static int BulkInsertRecordsSqlCe(Database db, Database.PocoData pd, IEnumerable collection) - { + { var cols = pd.Columns.ToArray(); using (var cmd = db.CreateCommand(db.Connection, string.Empty)) @@ -457,7 +457,7 @@ namespace Umbraco.Core.Persistence // This seems to cause problems, I think this is primarily used for retrieval, not // inserting. see: https://msdn.microsoft.com/en-us/library/system.data.sqlserverce.sqlcecommand.indexname%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396 //sqlCeCommand.IndexName = pd.TableInfo.PrimaryKey; - + var count = 0; using (var rs = sqlCeCommand.ExecuteResultSet(ResultSetOptions.Updatable)) { @@ -480,7 +480,7 @@ namespace Umbraco.Core.Persistence } return count; } - + } } From 95f5773046b56c42789aca3280abb33ba18aea97 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Nov 2016 08:23:53 +0100 Subject: [PATCH 84/88] U4-9188 Remove unnecessary duplicate index IX_cmsPropertyData --- .../Models/Rdbms/PropertyDataDto.cs | 1 - .../RemovePropertyDataIdIndex.cs | 38 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs index c4cd28f6e0..e9c1685bb3 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs @@ -11,7 +11,6 @@ namespace Umbraco.Core.Models.Rdbms { [Column("id")] [PrimaryKeyColumn] - [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyData")] public int Id { get; set; } [Column("contentNodeId")] diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs new file mode 100644 index 0000000000..b50c8e5f94 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs @@ -0,0 +1,38 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + /// + /// See: http://issues.umbraco.org/issue/U4-9188 + /// + [Migration("7.6.0", 0, GlobalSettings.UmbracoMigrationName)] + public class UpdateUniqueIndexOnCmsPropertyData : MigrationBase + { + public UpdateUniqueIndexOnCmsPropertyData(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + //tuple = tablename, indexname, columnname, unique + var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); + var found = indexes.FirstOrDefault( + x => x.Item1.InvariantEquals("cmsPropertyData") + && x.Item2.InvariantEquals("IX_cmsPropertyData")); + + if (found != null) + { + //drop the index + Delete.Index("IX_cmsPropertyData").OnTable("cmsPropertyData"); + } + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d76cad8a1c..9dff5aa050 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -440,6 +440,7 @@ + From 7ea156d4229503ceb7245603dc49d142efbcb2b6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 16 Nov 2016 09:57:24 +0100 Subject: [PATCH 85/88] IEventMessagesFactory without HttpContext --- src/Umbraco.Core/Services/ServiceContext.cs | 6 +++ .../RequestLifespanMessagesFactory.cs | 40 +++++++++++++++++-- .../SingletonHttpContextAccessor.cs | 6 ++- src/Umbraco.Web/UmbracoContextExtensions.cs | 5 +-- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index a18df0ade1..f2969de3be 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -176,6 +176,8 @@ namespace Umbraco.Core.Services if (logger == null) throw new ArgumentNullException("logger"); if (eventMessagesFactory == null) throw new ArgumentNullException("eventMessagesFactory"); + EventMessagesFactory = eventMessagesFactory; + BuildServiceCache(dbUnitOfWorkProvider, fileUnitOfWorkProvider, publishingStrategy, cache, repositoryFactory, logger, eventMessagesFactory); @@ -193,6 +195,8 @@ namespace Umbraco.Core.Services ILogger logger, IEventMessagesFactory eventMessagesFactory) { + EventMessagesFactory = eventMessagesFactory; + var provider = dbUnitOfWorkProvider; var fileProvider = fileUnitOfWorkProvider; @@ -316,6 +320,8 @@ namespace Umbraco.Core.Services _redirectUrlService = new Lazy(() => new RedirectUrlService(provider, repositoryFactory, logger, eventMessagesFactory)); } + internal IEventMessagesFactory EventMessagesFactory { get; private set; } + /// /// Gets the /// diff --git a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs index 26ac3bd5df..cf75e60755 100644 --- a/src/Umbraco.Web/RequestLifespanMessagesFactory.cs +++ b/src/Umbraco.Web/RequestLifespanMessagesFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.Remoting.Messaging; using Umbraco.Core.Events; namespace Umbraco.Web @@ -8,6 +9,7 @@ namespace Umbraco.Web ///
    internal class RequestLifespanMessagesFactory : IEventMessagesFactory { + private const string ContextKey = "Umbraco.Web.RequestLifespanMessagesFactory"; private readonly IHttpContextAccessor _httpAccessor; public RequestLifespanMessagesFactory(IHttpContextAccessor httpAccessor) @@ -18,11 +20,43 @@ namespace Umbraco.Web public EventMessages Get() { - if (_httpAccessor.Value.Items[typeof (RequestLifespanMessagesFactory).Name] == null) + var httpContext = _httpAccessor.Value; + if (httpContext != null) { - _httpAccessor.Value.Items[typeof(RequestLifespanMessagesFactory).Name] = new EventMessages(); + var eventMessages = httpContext.Items[ContextKey] as EventMessages; + if (eventMessages == null) httpContext.Items[ContextKey] = eventMessages = new EventMessages(); + return eventMessages; } - return (EventMessages)_httpAccessor.Value.Items[typeof (RequestLifespanMessagesFactory).Name]; + + var lccContext = CallContext.LogicalGetData(ContextKey) as EventMessages; + if (lccContext != null) return lccContext; + + throw new Exception("Could not get messages."); + } + + public EventMessages TryGet() + { + var httpContext = _httpAccessor.Value; + return httpContext != null + ? httpContext.Items[ContextKey] as EventMessages + : CallContext.LogicalGetData(ContextKey) as EventMessages; + } + + // Deploy wants to execute things outside of a request, where this factory would fail, + // so the factory is extended so that Deploy can Set/Clear event messages in the logical + // call context (which flows with async) - it needs to be set and cleared because, contrary + // to http context, it's not being cleared at the end of anything. + // + // to be refactored in v8! the whole IEventMessagesFactory is borked anyways + + public void SetLlc() + { + CallContext.LogicalSetData(ContextKey, new EventMessages()); + } + + public void ClearLlc() + { + CallContext.FreeNamedDataSlot(ContextKey); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/SingletonHttpContextAccessor.cs b/src/Umbraco.Web/SingletonHttpContextAccessor.cs index cdeafa48e1..3d6ac159fb 100644 --- a/src/Umbraco.Web/SingletonHttpContextAccessor.cs +++ b/src/Umbraco.Web/SingletonHttpContextAccessor.cs @@ -6,7 +6,11 @@ namespace Umbraco.Web { public HttpContextBase Value { - get { return new HttpContextWrapper(HttpContext.Current); } + get + { + var httpContext = HttpContext.Current; + return httpContext == null ? null : new HttpContextWrapper(httpContext); + } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoContextExtensions.cs b/src/Umbraco.Web/UmbracoContextExtensions.cs index 8cd38df6d1..53ef92690e 100644 --- a/src/Umbraco.Web/UmbracoContextExtensions.cs +++ b/src/Umbraco.Web/UmbracoContextExtensions.cs @@ -53,9 +53,8 @@ namespace Umbraco.Web /// public static EventMessages GetCurrentEventMessages(this UmbracoContext umbracoContext) { - var msgs = umbracoContext.HttpContext.Items[typeof (RequestLifespanMessagesFactory).Name]; - if (msgs == null) return null; - return (EventMessages) msgs; + var eventMessagesFactory = umbracoContext.Application.Services.EventMessagesFactory as RequestLifespanMessagesFactory; + return eventMessagesFactory == null ? null : eventMessagesFactory.TryGet(); } } From 27eaa8d4afe98671b6560db9aa83bcb1b8b72ea0 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 16 Nov 2016 15:19:15 +0100 Subject: [PATCH 86/88] Inject filesystems into partial repositories --- .../Repositories/PartialViewMacroRepository.cs | 8 +------- .../Persistence/Repositories/PartialViewRepository.cs | 11 +++-------- src/Umbraco.Core/Persistence/RepositoryFactory.cs | 4 ++-- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs index 827bee68ef..ea79ff6b82 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewMacroRepository.cs @@ -6,15 +6,9 @@ namespace Umbraco.Core.Persistence.Repositories { internal class PartialViewMacroRepository : PartialViewRepository { - public PartialViewMacroRepository(IUnitOfWork work) - : this(work, FileSystemProviderManager.Current.MacroPartialsFileSystem) - { - } - public PartialViewMacroRepository(IUnitOfWork work, IFileSystem fileSystem) : base(work, fileSystem) - { - } + { } protected override PartialViewType ViewType { get { return PartialViewType.PartialViewMacro; } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs index 199adb51ba..63cfd53833 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PartialViewRepository.cs @@ -10,14 +10,9 @@ namespace Umbraco.Core.Persistence.Repositories { internal class PartialViewRepository : FileRepository, IPartialViewRepository { - public PartialViewRepository(IUnitOfWork work) - : this(work, FileSystemProviderManager.Current.PartialViewsFileSystem) - { - } - - public PartialViewRepository(IUnitOfWork work, IFileSystem fileSystem) : base(work, fileSystem) - { - } + public PartialViewRepository(IUnitOfWork work, IFileSystem fileSystem) + : base(work, fileSystem) + { } protected virtual PartialViewType ViewType { get { return PartialViewType.PartialView; } } diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 71e0f57dfe..e04c898015 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -217,12 +217,12 @@ namespace Umbraco.Core.Persistence internal virtual IPartialViewRepository CreatePartialViewRepository(IUnitOfWork uow) { - return new PartialViewRepository(uow); + return new PartialViewRepository(uow, FileSystemProviderManager.Current.PartialViewsFileSystem); } internal virtual IPartialViewRepository CreatePartialViewMacroRepository(IUnitOfWork uow) { - return new PartialViewMacroRepository(uow); + return new PartialViewMacroRepository(uow, FileSystemProviderManager.Current.MacroPartialsFileSystem); } public virtual IStylesheetRepository CreateStylesheetRepository(IUnitOfWork uow, IDatabaseUnitOfWork db) From 568a4987e398183e7c4cc92a0ace41e9b58045b8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 16 Nov 2016 17:32:29 +0100 Subject: [PATCH 87/88] U4-9195 - fix PetaPoco page of int --- src/Umbraco.Core/Persistence/PetaPoco.cs | 19 ++++++++++++--- .../SqlSyntax/SqlServerSyntaxProvider.cs | 23 ++++++++----------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 63278889a2..88ff456ff6 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -739,6 +739,11 @@ namespace Umbraco.Core.Persistence /// internal virtual void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage) { + // this is overriden in UmbracoDatabase, and if running SqlServer >=2012, the database type + // is switched from SqlServer to SqlServerCE in order to use the better paging syntax that + // SqlCE supports, and SqlServer >=2012 too. + // so the first case is actually for SqlServer <2012, and second case is CE *and* SqlServer >=2012 + if (databaseType == DBType.SqlServer || databaseType == DBType.Oracle) { sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); @@ -746,8 +751,16 @@ namespace Umbraco.Core.Persistence { sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; } - sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", - sqlOrderBy == null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1); + + // split to ensure that peta_rn is the last field to be selected, else Page would fail + // the resulting sql is not perfect, NPoco has a much nicer way to do it, but it would require + // importing large parts of NPoco + var pos = sqlSelectRemoved.IndexOf("FROM"); + var sqlColumns = sqlSelectRemoved.Substring(0, pos); + var sqlFrom = sqlSelectRemoved.Substring(pos); + + sqlPage = string.Format("SELECT * FROM (SELECT {0}, ROW_NUMBER() OVER ({1}) peta_rn {2}) peta_paged WHERE peta_rn>@{3} AND peta_rn<=@{4}", + sqlColumns, sqlOrderBy ?? "ORDER BY (SELECT NULL)", sqlFrom, args.Length, args.Length + 1); args = args.Concat(new object[] { skip, skip + take }).ToArray(); } else if (databaseType == DBType.SqlServerCE) @@ -774,7 +787,7 @@ namespace Umbraco.Core.Persistence throw new Exception("Unable to parse SQL statement for paged query"); if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*")) throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); - + BuildSqlDbSpecificPagingQuery(_dbType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 4c35c90e1b..6cc97bdfb3 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -6,16 +6,11 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Persistence.SqlSyntax { /// - /// Represents an SqlSyntaxProvider for Sql Server + /// Represents an SqlSyntaxProvider for Sql Server. /// - [SqlSyntaxProviderAttribute(Constants.DatabaseProviders.SqlServer)] + [SqlSyntaxProvider(Constants.DatabaseProviders.SqlServer)] public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase { - public SqlServerSyntaxProvider() - { - - } - /// /// Gets/sets the version of the current SQL server instance /// @@ -31,7 +26,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax switch (firstPart) { case "13": - _versionName = SqlServerVersionName.V2014; + _versionName = SqlServerVersionName.V2016; break; case "12": _versionName = SqlServerVersionName.V2014; @@ -75,7 +70,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax { var items = db.Fetch("SELECT TableName = t.Name,ColumnName = c.Name,dc.Name,dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id"); return items.Select(x => new Tuple(x.TableName, x.ColumnName, x.Name, x.Definition)); - } + } public override IEnumerable GetTablesInSchema(Database db) { @@ -120,9 +115,9 @@ from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id inner join sys.all_columns as AC on IC.[object_id] = AC.[object_id] and IC.[column_id] = AC.[column_id] WHERE I.name NOT LIKE 'PK_%' order by T.name, I.name"); - return items.Select(item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, + return items.Select(item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE == 1)).ToList(); - + } public override bool DoesTableExist(Database db, string tableName) @@ -164,7 +159,7 @@ order by T.name, I.name"); switch (systemMethod) { case SystemMethods.NewGuid: - return "NEWID()"; + return "NEWID()"; case SystemMethods.CurrentDateTime: return "GETDATE()"; //case SystemMethods.NewSequentialId: @@ -181,11 +176,11 @@ order by T.name, I.name"); get { return "ALTER TABLE [{0}] DROP CONSTRAINT [DF_{0}_{1}]"; } } - + public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } public override string RenameColumn { get { return "sp_rename '{0}.{1}', '{2}', 'COLUMN'"; } } - + } } \ No newline at end of file From 0a883ae1091cbc31c9587016eb91ec442f5a94ee Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 16 Nov 2016 18:54:42 +0100 Subject: [PATCH 88/88] Make sure Umbraco runs in Visual Studio 2017 --- .gitignore | 1 + src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/umbraco.presentation.targets | 4 ++++ src/umbraco.sln | 2 ++ 5 files changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index 5b5e7660c5..16994e0ab1 100644 --- a/.gitignore +++ b/.gitignore @@ -141,3 +141,4 @@ build/docs.zip build/ui-docs.zip build/csharp-docs.zip build/msbuild.log +.vs/ diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index e2041e646e..791dd9b270 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2398,6 +2398,7 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index fef404067f..cf188dab8e 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -2250,6 +2250,7 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 diff --git a/src/umbraco.presentation.targets b/src/umbraco.presentation.targets index c38a1da485..2a33705d6f 100644 --- a/src/umbraco.presentation.targets +++ b/src/umbraco.presentation.targets @@ -55,6 +55,10 @@ $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.Tasks.dll + + + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.Tasks.dll +