Merge branch 'dev-v7-contenttypeeditor' into dev-v7-contenttypeeditor-validation

This commit is contained in:
Shannon
2015-09-25 14:27:10 +02:00
7 changed files with 175 additions and 193 deletions

View File

@@ -36,11 +36,9 @@ namespace Umbraco.Core.Persistence.Repositories
_contentTypeRepository = contentTypeRepository;
_preValRepository = new DataTypePreValueRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax);
EnsureUniqueNaming = true;
}
public bool EnsureUniqueNaming { get; set; }
#region Overrides of RepositoryBase<int,DataTypeDefinition>
protected override IDataTypeDefinition PerformGet(int id)
@@ -429,8 +427,7 @@ AND umbracoNode.id <> @id",
private string EnsureUniqueNodeName(string nodeName, int id = 0)
{
if (EnsureUniqueNaming == false)
return nodeName;
var sql = new Sql();
sql.Select("*")

View File

@@ -26,11 +26,8 @@ namespace Umbraco.Core
/// </summary>
public static class TypeFinder
{
private static readonly HashSet<Assembly> LocalFilteredAssemblyCache = new HashSet<Assembly>();
private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim();
private static HashSet<Assembly> _allAssemblies = null;
private static HashSet<Assembly> _binFolderAssemblies = null;
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
private static volatile HashSet<Assembly> _localFilteredAssemblyCache = null;
private static readonly object LocalFilteredAssemblyCacheLocker = new object();
/// <summary>
/// lazily load a reference to all assemblies and only local assemblies.
@@ -46,162 +43,97 @@ namespace Umbraco.Core
/// </remarks>
internal static HashSet<Assembly> GetAllAssemblies()
{
using (var lck = new UpgradeableReadLock(Locker))
return AllAssemblies.Value;
}
//Lazy access to the all assemblies list
private static readonly Lazy<HashSet<Assembly>> AllAssemblies = new Lazy<HashSet<Assembly>>(() =>
{
HashSet<Assembly> assemblies = null;
try
{
if (_allAssemblies == null)
var isHosted = HttpContext.Current != null;
try
{
if (isHosted)
{
assemblies = new HashSet<Assembly>(BuildManager.GetReferencedAssemblies().Cast<Assembly>());
}
}
catch (InvalidOperationException e)
{
if (!(e.InnerException is SecurityException))
throw;
}
lck.UpgradeToWriteLock();
if (assemblies == null)
{
//NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have
// already been loaded in to the app domain, instead we will look directly into the bin folder and load each one.
var binFolder = IOHelper.GetRootDirectoryBinFolder();
var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList();
//var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory;
//var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList();
assemblies = new HashSet<Assembly>();
foreach (var a in binAssemblyFiles)
{
try
{
var assName = AssemblyName.GetAssemblyName(a);
var ass = Assembly.Load(assName);
assemblies.Add(ass);
}
catch (Exception e)
{
if (e is SecurityException || e is BadImageFormatException)
{
//swallow these exceptions
}
else
{
throw;
}
}
}
}
HashSet<Assembly> assemblies = null;
//if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies.
if (!assemblies.Any())
{
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
assemblies.Add(a);
}
}
//here we are trying to get the App_Code assembly
var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported
var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code")));
//check if the folder exists and if there are any files in it with the supported file extensions
if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())))
{
try
{
var isHosted = HttpContext.Current != null;
try
{
if (isHosted)
{
assemblies = new HashSet<Assembly>(BuildManager.GetReferencedAssemblies().Cast<Assembly>());
}
}
catch (InvalidOperationException e)
{
if (!(e.InnerException is SecurityException))
throw;
}
if (assemblies == null)
{
//NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have
// already been loaded in to the app domain, instead we will look directly into the bin folder and load each one.
var binFolder = IOHelper.GetRootDirectoryBinFolder();
var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList();
//var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory;
//var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList();
assemblies = new HashSet<Assembly>();
foreach (var a in binAssemblyFiles)
{
try
{
var assName = AssemblyName.GetAssemblyName(a);
var ass = Assembly.Load(assName);
assemblies.Add(ass);
}
catch (Exception e)
{
if (e is SecurityException || e is BadImageFormatException)
{
//swallow these exceptions
}
else
{
throw;
}
}
}
}
//if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies.
if (!assemblies.Any())
{
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
assemblies.Add(a);
}
}
//here we are trying to get the App_Code assembly
var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported
var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code")));
//check if the folder exists and if there are any files in it with the supported file extensions
if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())))
{
var appCodeAssembly = Assembly.Load("App_Code");
if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already
assemblies.Add(appCodeAssembly);
}
//now set the _allAssemblies
_allAssemblies = new HashSet<Assembly>(assemblies);
var appCodeAssembly = Assembly.Load("App_Code");
if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already
assemblies.Add(appCodeAssembly);
}
catch (InvalidOperationException e)
catch (FileNotFoundException ex)
{
if (!(e.InnerException is SecurityException))
throw;
_binFolderAssemblies = _allAssemblies;
//this will occur if it cannot load the assembly
LogHelper.Error(typeof(TypeFinder), "Could not load assembly App_Code", ex);
}
}
return _allAssemblies;
}
}
/// <summary>
/// Returns only assemblies found in the bin folder that have been loaded into the app domain.
/// </summary>
/// <returns></returns>
/// <remarks>
/// This will be used if we implement App_Plugins from Umbraco v5 but currently it is not used.
/// </remarks>
internal static HashSet<Assembly> GetBinAssemblies()
{
if (_binFolderAssemblies == null)
catch (InvalidOperationException e)
{
using (new WriteLock(Locker))
{
var assemblies = GetAssembliesWithKnownExclusions().ToArray();
var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory;
var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList();
var domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName);
var safeDomainAssemblies = new HashSet<Assembly>();
var binFolderAssemblies = new HashSet<Assembly>();
foreach (var a in assemblies)
{
try
{
//do a test to see if its queryable in med trust
var assemblyFile = a.GetAssemblyFile();
safeDomainAssemblies.Add(a);
}
catch (SecurityException)
{
//we will just ignore this because this will fail
//in medium trust for system assemblies, we get an exception but we just want to continue until we get to
//an assembly that is ok.
}
}
foreach (var assemblyName in domainAssemblyNames)
{
try
{
var foundAssembly =
safeDomainAssemblies.FirstOrDefault(a => a.GetAssemblyFile() == assemblyName.GetAssemblyFile());
if (foundAssembly != null)
{
binFolderAssemblies.Add(foundAssembly);
}
}
catch (SecurityException)
{
//we will just ignore this because if we are trying to do a call to:
// AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName)))
//in medium trust for system assemblies, we get an exception but we just want to continue until we get to
//an assembly that is ok.
}
}
_binFolderAssemblies = new HashSet<Assembly>(binFolderAssemblies);
}
if (!(e.InnerException is SecurityException))
throw;
}
return _binFolderAssemblies;
}
return assemblies;
});
/// <summary>
/// Return a list of found local Assemblies excluding the known assemblies we don't want to scan
@@ -213,20 +145,23 @@ namespace Umbraco.Core
internal static HashSet<Assembly> GetAssembliesWithKnownExclusions(
IEnumerable<Assembly> excludeFromResults = null)
{
using (var lck = new UpgradeableReadLock(LocalFilteredAssemblyCacheLocker))
if (_localFilteredAssemblyCache == null)
{
if (LocalFilteredAssemblyCache.Any()) return LocalFilteredAssemblyCache;
lck.UpgradeToWriteLock();
var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter);
foreach (var a in assemblies)
lock (LocalFilteredAssemblyCacheLocker)
{
LocalFilteredAssemblyCache.Add(a);
//double check
if (_localFilteredAssemblyCache == null)
{
_localFilteredAssemblyCache = new HashSet<Assembly>();
var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter);
foreach (var a in assemblies)
{
_localFilteredAssemblyCache.Add(a);
}
}
}
return LocalFilteredAssemblyCache;
}
return _localFilteredAssemblyCache;
}
/// <summary>

View File

@@ -2,13 +2,32 @@
/**
* @ngdoc service
* @name umbraco.services.contentEditingHelper
* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by
* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by
* all editors to share logic and reduce the amount of replicated code among editors.
**/
function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, serverValidationManager, dialogService, formHelper, appState, keyboardService) {
function isValidIdentifier(id){
//empty id <= 0
if(angular.isNumber(id) && id > 0){
return true;
}
//empty guid
if(id === "00000000-0000-0000-0000-000000000000"){
return false;
}
//empty string / alias
if(id === ""){
return false;
}
return true;
}
return {
/** Used by the content editor and mini content editor to perform saving operations */
contentEditorPerformSave: function (args) {
if (!angular.isObject(args)) {
@@ -30,7 +49,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
var self = this;
var deferred = $q.defer();
if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage })) {
args.scope.busy = true;
@@ -71,9 +90,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
return deferred.promise;
},
/** Returns the action button definitions based on what permissions the user has.
The content.allowedActions parameter contains a list of chars, each represents a button by permission so
The content.allowedActions parameter contains a list of chars, each represents a button by permission so
here we'll build the buttons according to the chars of the user. */
configureContentEditorButtons: function (args) {
@@ -136,7 +155,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
handler: args.methods.unPublish
};
default:
return null;
return null;
}
}
@@ -160,8 +179,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
//Now we need to make the drop down button list, this is also slightly tricky because:
//We cannot have any buttons if there's no default button above.
//We cannot have the unpublish button (Z) when there's no publish permission.
//We cannot have the unpublish button (Z) when the item is not published.
//We cannot have the unpublish button (Z) when there's no publish permission.
//We cannot have the unpublish button (Z) when the item is not published.
if (buttons.defaultButton) {
//get the last index of the button order
@@ -174,7 +193,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
}
//if we are not creating, then we should add unpublish too,
//if we are not creating, then we should add unpublish too,
// so long as it's already published and if the user has access to publish
if (!args.create) {
if (args.content.publishDate && _.contains(args.content.allowedActions, "U")) {
@@ -235,13 +254,13 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
}
}
}
actions.push(defaultAction);
//Now we need to make the drop down button list, this is also slightly tricky because:
//We cannot have any buttons if there's no default button above.
//We cannot have the unpublish button (Z) when there's no publish permission.
//We cannot have the unpublish button (Z) when the item is not published.
//We cannot have the unpublish button (Z) when there's no publish permission.
//We cannot have the unpublish button (Z) when the item is not published.
if (defaultAction) {
//get the last index of the button order
var lastIndex = _.indexOf(actionOrder, defaultAction);
@@ -253,7 +272,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
}
}
//if we are not creating, then we should add unpublish too,
//if we are not creating, then we should add unpublish too,
// so long as it's already published and if the user has access to publish
if (!creating) {
if (content.publishDate && _.contains(content.allowedActions,"U")) {
@@ -329,7 +348,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
var allOrigProps = this.getAllProps(origContent);
var allNewProps = this.getAllProps(savedContent);
function getNewProp(alias) {
function getNewProp(alias) {
return _.find(allNewProps, function (item) {
return item.alias === alias;
});
@@ -343,12 +362,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
};
//check for changed built-in properties of the content
for (var o in origContent) {
//ignore the ones listed in the array
if (shouldIgnore(o)) {
continue;
}
if (!_.isEqual(origContent[o], savedContent[o])) {
origContent[o] = savedContent[o];
}
@@ -362,8 +381,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
//they have changed so set the origContent prop to the new one
var origVal = allOrigProps[p].value;
allOrigProps[p].value = newProp.value;
//instead of having a property editor $watch their expression to check if it has
//instead of having a property editor $watch their expression to check if it has
// been updated, instead we'll check for the existence of a special method on their model
// and just call it.
if (angular.isFunction(allOrigProps[p].onValueChanged)) {
@@ -388,7 +407,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
* A function to handle what happens when we have validation issues from the server side
*/
handleSaveError: function (args) {
if (!args.err) {
throw "args.err cannot be null";
}
@@ -402,7 +421,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
if (args.err.status === 400) {
//now we need to look through all the validation errors
if (args.err.data && (args.err.data.ModelState)) {
//wire up the server validation errs
formHelper.handleServerValidation(args.err.data.ModelState);
@@ -414,7 +433,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
if (args.rebindCallback && angular.isFunction(args.rebindCallback)) {
args.rebindCallback();
}
serverValidationManager.executeAndClearAllSubscriptions();
}
@@ -453,7 +472,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
}
if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) {
//we are not redirecting because this is not new content, it is existing content. In this case
// we need to detect what properties have changed and re-bind them with the server data.
//call the callback
@@ -471,14 +490,14 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
*
* @description
* Changes the location to be editing the newly created content after create was successful.
* We need to decide if we need to redirect to edito mode or if we will remain in create mode.
* We need to decide if we need to redirect to edito mode or if we will remain in create mode.
* We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID
*/
redirectToCreatedContent: function (id, modelState) {
//only continue if we are currently in create mode and if there is no 'Name' modelstate errors
// since we need at least a name to create content.
if ($routeParams.create && (id > 0 && (!modelState || !modelState["Name"]))) {
if ($routeParams.create && (isValidIdentifier(id) && (!modelState || !modelState["Name"]))) {
//need to change the location to not be in 'create' mode. Currently the route will be something like:
// /belle/#/content/edit/1234?doctype=newsArticle&create=true
@@ -487,7 +506,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
//clear the query strings
$location.search("");
//change to new path
$location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id);
//don't add a browser history for this
@@ -498,4 +517,4 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
}
};
}
angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper);
angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper);

View File

@@ -27,7 +27,7 @@ namespace Umbraco.Web.Trees
Constants.Applications.Media,
Constants.Applications.Members)]
[LegacyBaseTree(typeof (loadMembers))]
[Tree(Constants.Applications.Members, Constants.Trees.Members, "Members")]
[Tree(Constants.Applications.Members, Constants.Trees.Members)]
[PluginController("UmbracoTrees")]
[CoreTree]
public class MemberTreeController : TreeController

View File

@@ -8,6 +8,16 @@ namespace Umbraco.Web.Trees
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class TreeAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="TreeAttribute"/> class.
/// </summary>
/// <param name="appAlias">The app alias.</param>
/// <param name="alias">The alias.</param>
public TreeAttribute(string appAlias,
string alias) : this(appAlias, alias, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TreeAttribute"/> class.
/// </summary>
@@ -35,6 +45,8 @@ namespace Umbraco.Web.Trees
SortOrder = sortOrder;
}
public string ApplicationAlias { get; private set; }
public string Alias { get; private set; }
public string Title { get; private set; }

View File

@@ -1,8 +1,12 @@
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.Linq;
using System.Net.Http.Formatting;
using System.Threading;
using System.Web.Security;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Web.Models.Trees;
using Umbraco.Web.Mvc;
@@ -37,7 +41,22 @@ namespace Umbraco.Web.Trees
/// </summary>
public override string RootNodeDisplayName
{
get { return _attribute.Title; }
get
{
//if title is defined, return that
if(string.IsNullOrEmpty(_attribute.Title) == false)
return _attribute.Title;
//try to look up a tree header matching the tree alias
var culture = Security.CurrentUser.GetUserCulture(Services.TextService);
var localizedLabel = Services.TextService.Localize("treeHeaders/" + _attribute.Alias, culture);
if (string.IsNullOrEmpty(localizedLabel) == false)
return localizedLabel;
//is returned to signal that a label was not found
return "[" + _attribute.Alias + "]";
}
}
/// <summary>

View File

@@ -33,7 +33,7 @@ namespace umbraco.uicontrols {
base.CreateChildControls();
_tabList.TagName = "ul";
_tabList.Attributes.Add("class", "nav nav-tabs umb-nav-tabs span12");
_tabList.Attributes.Add("class", "nav nav-tabs umb-nav-tabs span12 -padding-left");
base.row.Controls.Add(_tabList);
_body.TagName = "div";
@@ -41,7 +41,7 @@ namespace umbraco.uicontrols {
base.Controls.Add(_body);
_tabsHolder.TagName = "div";
_tabsHolder.Attributes.Add("class", "tab-content form-horizontal umb-tab-content");
_tabsHolder.Attributes.Add("class", "tab-content form-horizontal");
_tabsHolder.ID = this.ID + "_content";
_body.Controls.Add(_tabsHolder);