This commit is contained in:
perploug
2013-11-12 19:29:15 +01:00
7 changed files with 462 additions and 135 deletions

View File

@@ -30,14 +30,6 @@ namespace Umbraco.Core.Models
//private static readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo<Template, int>(x => x.MasterTemplateId);
private static readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo<Template, string>(x => x.MasterTemplateAlias);
internal Template(string path)
: base(path)
{
base.Path = path;
ParentId = -1;
}
public Template(string path, string name, string alias)
: base(path)
{

View File

@@ -271,8 +271,62 @@ namespace Umbraco.Core.Persistence.Repositories
((ICanBeDirty)entity).ResetDirtyProperties();
}
private void PersistDeletedTemplate(TemplateDto dto)
{
//we need to get the real template for this item unfortunately to remove it
var template = Get(dto.NodeId);
if (template != null)
{
//NOTE: We must cast here so that it goes to the outter method to
// ensure the cache is updated.
PersistDeletedItem((IEntity)template);
}
}
/// <summary>
/// Returns a list of templates in order of descendants from the parent
/// </summary>
/// <param name="template"></param>
/// <param name="allChildTemplates"></param>
/// <returns></returns>
private static List<TemplateDto> GenerateTemplateHierarchy(TemplateDto template, IEnumerable<TemplateDto> allChildTemplates)
{
var hierarchy = new List<TemplateDto> {template};
foreach (var t in allChildTemplates.Where(x => x.Master == template.NodeId))
{
hierarchy.AddRange(GenerateTemplateHierarchy(t, allChildTemplates));
}
return hierarchy;
}
protected override void PersistDeletedItem(ITemplate entity)
{
//TODO: This isn't the most ideal way to delete a template tree, because below it will actually end up
// recursing back to this method for each descendant and re-looking up the template list causing an extrac
// SQL call - not ideal but there shouldn't ever be a heaping list of descendant templates.
//The easiest way to overcome this is to expose the underlying cache upwards so that the repository has access
// to it, then in the PersistDeletedTemplate we wouldn't recurse the underlying function, we'd just call
// PersistDeletedItem with a Template object and clear it's cache.
var sql = new Sql();
sql.Select("*").From<TemplateDto>().Where<TemplateDto>(dto => dto.Master != null || dto.NodeId == entity.Id);
var dtos = Database.Fetch<TemplateDto>(sql);
var self = dtos.Single(x => x.NodeId == entity.Id);
var allChildren = dtos.Except(new[] {self});
var hierarchy = GenerateTemplateHierarchy(self, allChildren);
//remove ourselves
hierarchy.Remove(self);
//change the order so it goes bottom up!
hierarchy.Reverse();
//delete the hierarchy
foreach (var descendant in hierarchy)
{
PersistDeletedTemplate(descendant);
}
//now we can delete this one
base.PersistDeletedItem(entity);
//Check for file under the Masterpages filesystem

View File

@@ -7,20 +7,25 @@ using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Persistence.Repositories
{
[TestFixture]
public class ScriptRepositoryTest
public class ScriptRepositoryTest : BaseUmbracoApplicationTest
{
private IFileSystem _fileSystem;
[SetUp]
public void Initialize()
public override void Initialize()
{
base.Initialize();
_fileSystem = new PhysicalFileSystem(SystemDirectories.Scripts);
var stream = CreateStream("Umbraco.Sys.registerNamespace(\"Umbraco.Utils\");");
_fileSystem.AddFile("test-script.js", stream);
using (var stream = CreateStream("Umbraco.Sys.registerNamespace(\"Umbraco.Utils\");"))
{
_fileSystem.AddFile("test-script.js", stream);
}
}
[Test]
@@ -93,6 +98,8 @@ namespace Umbraco.Tests.Persistence.Repositories
// Assert
Assert.IsFalse(repository.Exists("test-script.js"));
}
[Test]
@@ -180,8 +187,10 @@ namespace Umbraco.Tests.Persistence.Repositories
}
[TearDown]
public void TearDown()
public override void TearDown()
{
base.TearDown();
_fileSystem = null;
//Delete all files
var fs = new PhysicalFileSystem(SystemDirectories.Scripts);

View File

@@ -0,0 +1,268 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Caching;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Persistence.Repositories
{
[TestFixture]
public class TemplateRepositoryTest : BaseDatabaseFactoryTest
{
private IFileSystem _masterPageFileSystem;
private IFileSystem _viewsFileSystem;
[SetUp]
public override void Initialize()
{
base.Initialize();
_masterPageFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
_viewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
}
[Test]
public void Can_Instantiate_Repository_From_Resolver()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
// Act
var repository = RepositoryResolver.Current.ResolveByType<ITemplateRepository>(unitOfWork);
// Assert
Assert.That(repository, Is.Not.Null);
}
[Test]
public void Can_Instantiate_Repository()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
// Act
var repository = new TemplateRepository(unitOfWork, NullCacheProvider.Current, _masterPageFileSystem, _viewsFileSystem);
// Assert
Assert.That(repository, Is.Not.Null);
}
[Test]
public void Can_Perform_Add_MasterPage()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
var repository = new TemplateRepository(unitOfWork, NullCacheProvider.Current, _masterPageFileSystem, _viewsFileSystem);
// Act
var template = new Template("test-add-masterpage.master", "test", "test") { Content = @"<%@ Master Language=""C#"" %>" };
repository.AddOrUpdate(template);
unitOfWork.Commit();
//Assert
Assert.That(repository.Get("test"), Is.Not.Null);
Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.True);
}
[Test]
public void Can_Perform_Update_MasterPage()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
var repository = new TemplateRepository(unitOfWork, NullCacheProvider.Current, _masterPageFileSystem, _viewsFileSystem);
// Act
var template = new Template("test-updated-masterpage.master", "test", "test") { Content = @"<%@ Master Language=""C#"" %>" };
repository.AddOrUpdate(template);
unitOfWork.Commit();
template.Content = @"<%@ Master Language=""VB"" %>";
repository.AddOrUpdate(template);
unitOfWork.Commit();
var updated = repository.Get("test");
// Assert
Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.True);
Assert.That(updated.Content, Is.EqualTo(@"<%@ Master Language=""VB"" %>"));
}
[Test]
public void Can_Perform_Delete()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
var repository = new TemplateRepository(unitOfWork, NullCacheProvider.Current, _masterPageFileSystem, _viewsFileSystem);
var template = new Template("test-add-masterpage.master", "test", "test") { Content = @"<%@ Master Language=""C#"" %>" };
repository.AddOrUpdate(template);
unitOfWork.Commit();
// Act
var templates = repository.Get("test");
repository.Delete(templates);
unitOfWork.Commit();
// Assert
Assert.IsNull(repository.Get("test"));
}
[Test]
public void Can_Perform_Delete_On_Nested_Templates()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
var repository = new TemplateRepository(unitOfWork, NullCacheProvider.Current, _masterPageFileSystem, _viewsFileSystem);
var parent = new Template("test-parent-masterpage.master", "parent", "parent") { Content = @"<%@ Master Language=""C#"" %>" };
var child = new Template("test-child-masterpage.master", "child", "child") { Content = @"<%@ Master Language=""C#"" %>" };
var baby = new Template("test-baby-masterpage.master", "baby", "baby") { Content = @"<%@ Master Language=""C#"" %>" };
child.MasterTemplateAlias = parent.Alias;
child.MasterTemplateId = new Lazy<int>(() => parent.Id);
baby.MasterTemplateAlias = child.Alias;
baby.MasterTemplateId = new Lazy<int>(() => child.Id);
repository.AddOrUpdate(parent);
repository.AddOrUpdate(child);
repository.AddOrUpdate(baby);
unitOfWork.Commit();
// Act
var templates = repository.Get("parent");
repository.Delete(templates);
unitOfWork.Commit();
// Assert
Assert.IsNull(repository.Get("test"));
}
//[Test]
//public void Can_Perform_Get_On_ScriptRepository()
//{
// // Arrange
// var provider = new FileUnitOfWorkProvider();
// var unitOfWork = provider.GetUnitOfWork();
// var repository = new ScriptRepository(unitOfWork, _masterPageFileSystem);
// // Act
// var exists = repository.Get("test-script.js");
// // Assert
// Assert.That(exists, Is.Not.Null);
// Assert.That(exists.Alias, Is.EqualTo("test-script"));
// Assert.That(exists.Name, Is.EqualTo("test-script.js"));
//}
//[Test]
//public void Can_Perform_GetAll_On_ScriptRepository()
//{
// // Arrange
// var provider = new FileUnitOfWorkProvider();
// var unitOfWork = provider.GetUnitOfWork();
// var repository = new ScriptRepository(unitOfWork, _masterPageFileSystem);
// var script = new Script("test-script1.js") { Content = "/// <reference name=\"MicrosoftAjax.js\"/>" };
// repository.AddOrUpdate(script);
// var script2 = new Script("test-script2.js") { Content = "/// <reference name=\"MicrosoftAjax.js\"/>" };
// repository.AddOrUpdate(script2);
// var script3 = new Script("test-script3.js") { Content = "/// <reference name=\"MicrosoftAjax.js\"/>" };
// repository.AddOrUpdate(script3);
// unitOfWork.Commit();
// // Act
// var scripts = repository.GetAll();
// // Assert
// Assert.That(scripts, Is.Not.Null);
// Assert.That(scripts.Any(), Is.True);
// Assert.That(scripts.Any(x => x == null), Is.False);
// Assert.That(scripts.Count(), Is.EqualTo(4));
//}
//[Test]
//public void Can_Perform_GetAll_With_Params_On_ScriptRepository()
//{
// // Arrange
// var provider = new FileUnitOfWorkProvider();
// var unitOfWork = provider.GetUnitOfWork();
// var repository = new ScriptRepository(unitOfWork, _masterPageFileSystem);
// var script = new Script("test-script1.js") { Content = "/// <reference name=\"MicrosoftAjax.js\"/>" };
// repository.AddOrUpdate(script);
// var script2 = new Script("test-script2.js") { Content = "/// <reference name=\"MicrosoftAjax.js\"/>" };
// repository.AddOrUpdate(script2);
// var script3 = new Script("test-script3.js") { Content = "/// <reference name=\"MicrosoftAjax.js\"/>" };
// repository.AddOrUpdate(script3);
// unitOfWork.Commit();
// // Act
// var scripts = repository.GetAll("test-script1.js", "test-script2.js");
// // Assert
// Assert.That(scripts, Is.Not.Null);
// Assert.That(scripts.Any(), Is.True);
// Assert.That(scripts.Any(x => x == null), Is.False);
// Assert.That(scripts.Count(), Is.EqualTo(2));
//}
//[Test]
//public void Can_Perform_Exists_On_ScriptRepository()
//{
// // Arrange
// var provider = new FileUnitOfWorkProvider();
// var unitOfWork = provider.GetUnitOfWork();
// var repository = new ScriptRepository(unitOfWork, _masterPageFileSystem);
// // Act
// var exists = repository.Exists("test-script.js");
// // Assert
// Assert.That(exists, Is.True);
//}
[TearDown]
public override void TearDown()
{
base.TearDown();
_masterPageFileSystem = null;
_viewsFileSystem = null;
//Delete all files
var fsMaster = new PhysicalFileSystem(SystemDirectories.Masterpages);
var masterPages = fsMaster.GetFiles("", "*.master");
foreach (var file in masterPages)
{
fsMaster.DeleteFile(file);
}
var fsViews = new PhysicalFileSystem(SystemDirectories.MvcViews);
var views = fsMaster.GetFiles("", "*.cshtml");
foreach (var file in views)
{
fsMaster.DeleteFile(file);
}
}
protected Stream CreateStream(string contents = null)
{
if (string.IsNullOrEmpty(contents))
contents = "/* test */";
var bytes = Encoding.UTF8.GetBytes(contents);
var stream = new MemoryStream(bytes);
return stream;
}
}
}

View File

@@ -262,6 +262,7 @@
<Compile Include="Persistence\Repositories\MemberRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\MemberTypeRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\TagRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\TemplateRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\UserRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\UserTypeRepositoryTest.cs" />
<Compile Include="Models\Mapping\ContentWebModelMappingTests.cs" />

View File

@@ -1,121 +1,124 @@
/**
* @ngdoc controller
* @name Umbraco.MainController
* @function
*
* @description
* The main application controller
*
*/
function MainController($scope, $rootScope, $location, $routeParams, $rootScope, $timeout, $http, $log, notificationsService, userService, navigationService, historyService, legacyJsLoader, updateChecker) {
var legacyTreeJsLoaded = false;
//detect if the current device is touch-enabled
//todo, move this out of the controller
$rootScope.touchDevice = ("ontouchstart" in window || window.touch || window.navigator.msMaxTouchPoints===5 || window.DocumentTouch && document instanceof DocumentTouch);
navigationService.touchDevice = $rootScope.touchDevice;
//the null is important because we do an explicit bool check on this in the view
//the avatar is by default the umbraco logo
$scope.authenticated = null;
$scope.avatar = "assets/img/application/logo.png";
//subscribes to notifications in the notification service
$scope.notifications = notificationsService.current;
$scope.$watch('notificationsService.current', function (newVal, oldVal, scope) {
if (newVal) {
$scope.notifications = newVal;
}
});
$scope.removeNotification = function (index) {
notificationsService.remove(index);
};
$scope.closeDialogs = function (event) {
//only close dialogs if non-link and non-buttons are clicked
var el = event.target.nodeName;
var els = ["INPUT","A","BUTTON"];
if(els.indexOf(el) >= 0){return;}
var parents = $(event.target).parents("a,button");
if(parents.length > 0){
return;
}
//SD: I've updated this so that we don't close the dialog when clicking inside of the dialog
var nav = $(event.target).parents("#navigation");
if (nav.length === 1) {
return;
}
$rootScope.$emit("closeDialogs", event);
};
//when a user logs out or timesout
$scope.$on("notAuthenticated", function() {
$scope.authenticated = null;
$scope.user = null;
});
//when a user is authorized setup the data
$scope.$on("authenticated", function (evt, data) {
//We need to load in the legacy tree js but only once no matter what user has logged in
if (!legacyTreeJsLoaded) {
legacyJsLoader.loadLegacyTreeJs($scope).then(
function (result) {
legacyTreeJsLoaded = true;
//TODO: We could wait for this to load before running the UI ?
});
}
$scope.authenticated = data.authenticated;
$scope.user = data.user;
updateChecker.check().then(function(update){
if(update && update !== "null"){
if(update.type !== "None"){
var notification = {
headline: "Update available",
message: "Click to download",
sticky: true,
type: "info",
url: update.url
};
notificationsService.add(notification);
}
}
});
//if the user has changed we need to redirect to the root so they don't try to continue editing the
//last item in the URL
if (data.lastUserId && data.lastUserId !== data.user.id) {
$location.path("/").search("");
historyService.removeAll();
}
if($scope.user.emailHash){
$timeout(function(){
//yes this is wrong..
$("#avatar-img").fadeTo(1000, 0, function(){
$timeout(function(){
$scope.avatar = "http://www.gravatar.com/avatar/" + $scope.user.emailHash +".jpg?s=64&d=mm";
});
$("#avatar-img").fadeTo(1000, 1);
});
}, 3000);
}
});
}
//register it
angular.module('umbraco').controller("Umbraco.MainController", MainController);
/**
* @ngdoc controller
* @name Umbraco.MainController
* @function
*
* @description
* The main application controller
*
*/
function MainController($scope, $rootScope, $location, $routeParams, $rootScope, $timeout, $http, $log, notificationsService, userService, navigationService, historyService, legacyJsLoader, updateChecker) {
var legacyTreeJsLoaded = false;
//detect if the current device is touch-enabled
//todo, move this out of the controller
$rootScope.touchDevice = ("ontouchstart" in window || window.touch || window.navigator.msMaxTouchPoints===5 || window.DocumentTouch && document instanceof DocumentTouch);
navigationService.touchDevice = $rootScope.touchDevice;
//the null is important because we do an explicit bool check on this in the view
//the avatar is by default the umbraco logo
$scope.authenticated = null;
$scope.avatar = "assets/img/application/logo.png";
//subscribes to notifications in the notification service
$scope.notifications = notificationsService.current;
$scope.$watch('notificationsService.current', function (newVal, oldVal, scope) {
if (newVal) {
$scope.notifications = newVal;
}
});
$scope.removeNotification = function (index) {
notificationsService.remove(index);
};
$scope.closeDialogs = function (event) {
//only close dialogs if non-link and non-buttons are clicked
var el = event.target.nodeName;
var els = ["INPUT","A","BUTTON"];
if(els.indexOf(el) >= 0){return;}
var parents = $(event.target).parents("a,button");
if(parents.length > 0){
return;
}
//SD: I've updated this so that we don't close the dialog when clicking inside of the dialog
var nav = $(event.target).parents("#navigation");
if (nav.length === 1) {
return;
}
$rootScope.$emit("closeDialogs", event);
};
//when a user logs out or timesout
$scope.$on("notAuthenticated", function() {
$scope.authenticated = null;
$scope.user = null;
});
//when a user is authorized setup the data
$scope.$on("authenticated", function (evt, data) {
//We need to load in the legacy tree js but only once no matter what user has logged in
if (!legacyTreeJsLoaded) {
legacyJsLoader.loadLegacyTreeJs($scope).then(
function (result) {
legacyTreeJsLoaded = true;
//TODO: We could wait for this to load before running the UI ?
});
}
$scope.authenticated = data.authenticated;
$scope.user = data.user;
updateChecker.check().then(function(update){
if(update && update !== "null"){
if(update.type !== "None"){
var notification = {
headline: "Update available",
message: "Click to download",
sticky: true,
type: "info",
url: update.url
};
notificationsService.add(notification);
}
}
});
//if the user has changed we need to redirect to the root so they don't try to continue editing the
//last item in the URL
if (data.lastUserId && data.lastUserId !== data.user.id) {
$location.path("/").search("");
historyService.removeAll();
}
if($scope.user.emailHash){
$timeout(function () {
//yes this is wrong..
$("#avatar-img").fadeTo(1000, 0, function () {
$timeout(function () {
//this can be null if they time out
if ($scope.user && $scope.user.emailHash) {
$scope.avatar = "http://www.gravatar.com/avatar/" + $scope.user.emailHash + ".jpg?s=64&d=mm";
}
});
$("#avatar-img").fadeTo(1000, 1);
});
}, 3000);
}
});
}
//register it
angular.module('umbraco').controller("Umbraco.MainController", MainController);

View File

@@ -71,7 +71,7 @@ namespace Umbraco.Web.UI.Controls
var divMacroItemContainer = new TagBuilder("div");
divMacroItemContainer.Attributes.Add("style", "width: 285px;display:none;");
divMacroItemContainer.Attributes.Add("class", "sbMenu");
var macros = ApplicationContext.DatabaseContext.Database.Query<MacroDto>("select id, macroAlias, macroName from cmsMacro order by macroName");
var macros = ApplicationContext.DatabaseContext.Database.Fetch<MacroDto>("select id, macroAlias, macroName from cmsMacro order by macroName");
foreach (var macro in macros)
{
var divMacro = new TagBuilder("div");