diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec
index c52c8831e0..cf4958bc98 100644
--- a/build/NuSpecs/UmbracoCms.Web.nuspec
+++ b/build/NuSpecs/UmbracoCms.Web.nuspec
@@ -27,7 +27,7 @@
-
+
diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs
index 2d4042fad0..b53a2b8eaf 100644
--- a/src/Umbraco.Core/Constants-Applications.cs
+++ b/src/Umbraco.Core/Constants-Applications.cs
@@ -77,7 +77,7 @@
/// alias for the macro tree.
///
public const string Macros = "macros";
-
+
///
/// alias for the datatype tree.
///
@@ -92,7 +92,7 @@
/// alias for the dictionary tree.
///
public const string Dictionary = "dictionary";
-
+
public const string Stylesheets = "stylesheets";
///
@@ -121,7 +121,7 @@
public const string Templates = "templates";
public const string RelationTypes = "relationTypes";
-
+
public const string Languages = "languages";
///
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
index 5cc818a6d2..d8283fd112 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
@@ -50,8 +50,6 @@ namespace Umbraco.Core.Migrations.Install
typeof (MemberTypeDto),
typeof (MemberDto),
typeof (Member2MemberGroupDto),
- typeof (ContentXmlDto),
- typeof (PreviewXmlDto),
typeof (PropertyTypeGroupDto),
typeof (PropertyTypeDto),
typeof (PropertyDataDto),
@@ -139,13 +137,18 @@ namespace Umbraco.Core.Migrations.Install
/// Validates the schema of the current database.
///
internal DatabaseSchemaResult ValidateSchema()
+ {
+ return ValidateSchema(OrderedTables);
+ }
+
+ internal DatabaseSchemaResult ValidateSchema(IEnumerable orderedTables)
{
var result = new DatabaseSchemaResult(SqlSyntax);
result.IndexDefinitions.AddRange(SqlSyntax.GetDefinedIndexes(_database)
.Select(x => new DbIndexDefinition(x)));
- result.TableDefinitions.AddRange(OrderedTables
+ result.TableDefinitions.AddRange(orderedTables
.Select(x => DefinitionFactory.GetTableDefinition(x, SqlSyntax)));
ValidateDbTables(result);
diff --git a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs
index 6d936e8407..bf07e4d08f 100644
--- a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs
+++ b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs
@@ -96,6 +96,12 @@ namespace Umbraco.Core.Migrations
return tables.Any(x => x.InvariantEquals(tableName));
}
+ protected bool IndexExists(string indexName)
+ {
+ var indexes = SqlSyntax.GetDefinedIndexes(Context.Database);
+ return indexes.Any(x => x.Item2.InvariantEquals(indexName));
+ }
+
protected bool ColumnExists(string tableName, string columnName)
{
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray();
diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
index a9444a0918..0cd6ac8b16 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
@@ -127,7 +127,13 @@ namespace Umbraco.Core.Migrations.Upgrade
To("{ED28B66A-E248-4D94-8CDB-9BDF574023F0}");
To("{38C809D5-6C34-426B-9BEA-EFD39162595C}");
To("{6017F044-8E70-4E10-B2A3-336949692ADD}");
-
+ To("{98339BEF-E4B2-48A8-B9D1-D173DC842BBE}");
+
+ Merge()
+ .To("{CDBEDEE4-9496-4903-9CF2-4104E00FF960}")
+ .With()
+ .To("{940FD19A-00A8-4D5C-B8FF-939143585726}")
+ .As("{0576E786-5C30-4000-B969-302B61E90CA3}");
//FINAL
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs
new file mode 100644
index 0000000000..0ccc2d93ff
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs
@@ -0,0 +1,22 @@
+using Umbraco.Core.Persistence.Dtos;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
+{
+ public class AddUserLoginDtoDateIndex : MigrationBase
+ {
+ public AddUserLoginDtoDateIndex(IMigrationContext context)
+ : base(context)
+ { }
+
+ public override void Migrate()
+ {
+ if (!IndexExists("IX_umbracoUserLogin_lastValidatedUtc"))
+ Create.Index("IX_umbracoUserLogin_lastValidatedUtc")
+ .OnTable(UserLoginDto.TableName)
+ .OnColumn("lastValidatedUtc")
+ .Ascending()
+ .WithOptions().NonClustered()
+ .Do();
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs
index 008b3e4b5f..061b96976a 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs
@@ -1,6 +1,5 @@
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
{
-
public class DropTaskTables : MigrationBase
{
public DropTaskTables(IMigrationContext context)
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropXmlTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropXmlTables.cs
new file mode 100644
index 0000000000..be79178932
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropXmlTables.cs
@@ -0,0 +1,17 @@
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
+{
+ public class DropXmlTables : MigrationBase
+ {
+ public DropXmlTables(IMigrationContext context)
+ : base(context)
+ { }
+
+ public override void Migrate()
+ {
+ if (TableExists("cmsContentXml"))
+ Delete.Table("cmsContentXml").Do();
+ if (TableExists("cmsPreviewXml"))
+ Delete.Table("cmsPreviewXml").Do();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs
new file mode 100644
index 0000000000..5327a344a4
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models;
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Persistence.Dtos;
+using Umbraco.Core.PropertyEditors;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
+{
+ public class RadioAndCheckboxAndDropdownPropertyEditorsMigration : MigrationBase
+ {
+ public RadioAndCheckboxAndDropdownPropertyEditorsMigration(IMigrationContext context)
+ : base(context)
+ {
+ }
+
+ public override void Migrate()
+ {
+ var refreshCache = false;
+
+ refreshCache |= Migrate(Constants.PropertyEditors.Aliases.RadioButtonList, (dto, configuration) => UpdateRadioOrCheckboxPropertyDataDto(dto, configuration, true));
+ refreshCache |= Migrate(Constants.PropertyEditors.Aliases.CheckBoxList, (dto, configuration) => UpdateRadioOrCheckboxPropertyDataDto(dto, configuration, false));
+ refreshCache |= Migrate(Constants.PropertyEditors.Aliases.DropDownListFlexible, UpdateDropDownPropertyDataDto);
+
+ if (refreshCache)
+ {
+ //FIXME: trigger cache rebuild. Currently the data in the database tables is wrong.
+ }
+ }
+
+ private bool Migrate(string editorAlias, Func updateRadioPropertyDataFunc)
+ {
+ var refreshCache = false;
+ var dataTypes = GetDataTypes(editorAlias);
+
+ foreach (var dataType in dataTypes)
+ {
+ ValueListConfiguration config;
+
+ if (dataType.Configuration.IsNullOrWhiteSpace())
+ continue;
+
+ // parse configuration, and update everything accordingly
+ try
+ {
+ config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase(
+ dataType.Configuration);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(
+ ex,
+ "Invalid radio button configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared",
+ dataType.Configuration);
+
+ continue;
+ }
+
+ // get property data dtos
+ var propertyDataDtos = Database.Fetch(Sql()
+ .Select()
+ .From()
+ .InnerJoin()
+ .On((pt, pd) => pt.Id == pd.PropertyTypeId)
+ .InnerJoin()
+ .On((dt, pt) => dt.NodeId == pt.DataTypeId)
+ .Where(x => x.DataTypeId == dataType.NodeId));
+
+ // update dtos
+ var updatedDtos = propertyDataDtos.Where(x => updateRadioPropertyDataFunc(x, config));
+
+ // persist changes
+ foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto);
+
+ UpdateDataType(dataType);
+ refreshCache = true;
+ }
+
+ return refreshCache;
+ }
+
+ private List GetDataTypes(string editorAlias)
+ {
+ //need to convert the old drop down data types to use the new one
+ var dataTypes = Database.Fetch(Sql()
+ .Select()
+ .From()
+ .Where(x => x.EditorAlias == editorAlias));
+ return dataTypes;
+ }
+
+ private void UpdateDataType(DataTypeDto dataType)
+ {
+ dataType.DbType = ValueStorageType.Nvarchar.ToString();
+ Database.Update(dataType);
+ }
+
+ private bool UpdateRadioOrCheckboxPropertyDataDto(PropertyDataDto propData, ValueListConfiguration config, bool singleValue)
+ {
+ //Get the INT ids stored for this property/drop down
+ int[] ids = null;
+ if (!propData.VarcharValue.IsNullOrWhiteSpace())
+ {
+ ids = ConvertStringValues(propData.VarcharValue);
+ }
+ else if (!propData.TextValue.IsNullOrWhiteSpace())
+ {
+ ids = ConvertStringValues(propData.TextValue);
+ }
+ else if (propData.IntegerValue.HasValue)
+ {
+ ids = new[] {propData.IntegerValue.Value};
+ }
+
+ //if there are INT ids, convert them to values based on the configuration
+ if (ids == null || ids.Length <= 0) return false;
+
+ //map the ids to values
+ var values = new List();
+ var canConvert = true;
+
+ foreach (var id in ids)
+ {
+ var val = config.Items.FirstOrDefault(x => x.Id == id);
+ if (val != null)
+ values.Add(val.Value);
+ else
+ {
+ Logger.Warn(
+ "Could not find associated data type configuration for stored Id {DataTypeId}", id);
+ canConvert = false;
+ }
+ }
+
+ if (!canConvert) return false;
+
+ //The radio button only supports selecting a single value, so if there are multiple for some insane reason we can only use the first
+ propData.VarcharValue = singleValue ? values[0] : JsonConvert.SerializeObject(values);
+ propData.TextValue = null;
+ propData.IntegerValue = null;
+ return true;
+ }
+
+ private bool UpdateDropDownPropertyDataDto(PropertyDataDto propData, ValueListConfiguration config)
+ {
+ //Get the INT ids stored for this property/drop down
+ var values = propData.VarcharValue.Split(new []{","}, StringSplitOptions.RemoveEmptyEntries);
+
+ //if there are INT ids, convert them to values based on the configuration
+ if (values == null || values.Length <= 0) return false;
+
+ //The radio button only supports selecting a single value, so if there are multiple for some insane reason we can only use the first
+ propData.VarcharValue = JsonConvert.SerializeObject(values);
+ propData.TextValue = null;
+ propData.IntegerValue = null;
+ return true;
+ }
+
+ private int[] ConvertStringValues(string val)
+ {
+ var splitVals = val.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
+
+ var intVals = splitVals
+ .Select(x => int.TryParse(x, out var i) ? i : int.MinValue)
+ .Where(x => x != int.MinValue)
+ .ToArray();
+
+ //only return if the number of values are the same (i.e. All INTs)
+ if (splitVals.Length == intVals.Length)
+ return intVals;
+
+ return null;
+ }
+
+ private class ValueListConfigurationEditor : ConfigurationEditor
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
index a569954629..963ab7050f 100644
--- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
+++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
@@ -102,8 +102,8 @@ namespace Umbraco.Core.Packaging
{
// TODO: I don't think this ordering is necessary
var orderedTypes = (from contentType in contentTypes
- orderby contentType.ParentId descending, contentType.Id descending
- select contentType).ToList();
+ orderby contentType.ParentId descending, contentType.Id descending
+ select contentType).ToList();
removedContentTypes.AddRange(orderedTypes);
contentTypeService.Delete(orderedTypes, userId);
}
@@ -157,7 +157,7 @@ namespace Umbraco.Core.Packaging
DictionaryItemsUninstalled = removedDictionaryItems,
DataTypesUninstalled = removedDataTypes,
LanguagesUninstalled = removedLanguages,
-
+
};
return summary;
@@ -188,8 +188,8 @@ namespace Umbraco.Core.Packaging
var element = packageDocument.XmlData;
var roots = from doc in element.Elements()
- where (string)doc.Attribute("isDoc") == ""
- select doc;
+ where (string)doc.Attribute("isDoc") == ""
+ select doc;
var contents = ParseDocumentRootXml(roots, parentId, importedDocumentTypes).ToList();
if (contents.Any())
@@ -289,13 +289,26 @@ namespace Umbraco.Core.Packaging
var nodeName = element.Attribute("nodeName").Value;
var path = element.Attribute("path").Value;
var templateId = element.AttributeValue("template");
-
+
var properties = from property in element.Elements()
where property.Attribute("isDoc") == null
select property;
+ //TODO: This will almost never work, we can't reference a template by an INT Id within a package manifest, we need to change the
+ // packager to package templates by UDI and resolve by the same, in 98% of cases, this isn't going to work, or it will resolve the wrong template.
var template = templateId.HasValue ? _fileService.GetTemplate(templateId.Value) : null;
+ //now double check this is correct since its an INT it could very well be pointing to an invalid template :/
+ if (template != null)
+ {
+ if (!contentType.IsAllowedTemplate(template.Alias))
+ {
+ //well this is awkward, we'll set the template to null and it will be wired up to the default template
+ // when it's persisted in the document repository
+ template = null;
+ }
+ }
+
IContent content = parent == null
? new Content(nodeName, parentId, contentType)
{
@@ -312,6 +325,12 @@ namespace Umbraco.Core.Packaging
Key = key
};
+ //Here we make sure that we take composition properties in account as well
+ //otherwise we would skip them and end up losing content
+ var propTypes = contentType.CompositionPropertyTypes.Any()
+ ? contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x)
+ : contentType.PropertyTypes.ToDictionary(x => x.Alias, x => x);
+
foreach (var property in properties)
{
string propertyTypeAlias = property.Name.LocalName;
@@ -319,10 +338,11 @@ namespace Umbraco.Core.Packaging
{
var propertyValue = property.Value;
- var propertyType = contentType.PropertyTypes.FirstOrDefault(pt => pt.Alias == propertyTypeAlias);
-
- //set property value
- content.SetValue(propertyTypeAlias, propertyValue);
+ if (propTypes.TryGetValue(propertyTypeAlias, out var propertyType))
+ {
+ //set property value
+ content.SetValue(propertyTypeAlias, propertyValue);
+ }
}
}
@@ -335,7 +355,7 @@ namespace Umbraco.Core.Packaging
public IEnumerable ImportDocumentType(XElement docTypeElement, int userId)
{
- return ImportDocumentTypes(new []{ docTypeElement }, userId);
+ return ImportDocumentTypes(new[] { docTypeElement }, userId);
}
///
@@ -359,7 +379,7 @@ namespace Umbraco.Core.Packaging
public IEnumerable ImportDocumentTypes(IReadOnlyCollection unsortedDocumentTypes, bool importStructure, int userId)
{
var importedContentTypes = new Dictionary();
-
+
//When you are importing a single doc type we have to assume that the dependencies are already there.
//Otherwise something like uSync won't work.
var graph = new TopoGraph>(x => x.Key, x => x.Dependencies);
@@ -452,7 +472,7 @@ namespace Umbraco.Core.Packaging
if (updatedContentTypes.Any())
_contentTypeService.Save(updatedContentTypes, userId);
}
-
+
return list;
}
@@ -854,7 +874,7 @@ namespace Umbraco.Core.Packaging
{
_dataTypeService.Save(dataTypes, userId, true);
}
-
+
return dataTypes;
}
@@ -937,7 +957,7 @@ namespace Umbraco.Core.Packaging
var items = new List();
foreach (var dictionaryItemElement in dictionaryItemElementList)
items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, parentId, userId));
-
+
return items;
}
@@ -1024,7 +1044,7 @@ namespace Umbraco.Core.Packaging
_localizationService.Save(langauge, userId);
list.Add(langauge);
}
-
+
return list;
}
@@ -1187,7 +1207,7 @@ namespace Umbraco.Core.Packaging
public IEnumerable ImportTemplate(XElement templateElement, int userId)
{
- return ImportTemplates(new[] {templateElement}, userId);
+ return ImportTemplates(new[] { templateElement }, userId);
}
///
@@ -1234,7 +1254,7 @@ namespace Umbraco.Core.Packaging
var alias = templateElement.Element("Alias").Value;
var design = templateElement.Element("Design").Value;
var masterElement = templateElement.Element("Master");
-
+
var existingTemplate = _fileService.GetTemplate(alias) as Template;
var template = existingTemplate ?? new Template(templateName, alias);
template.Content = design;
diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
index b874c6e04a..b62a99ce83 100644
--- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
+++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
@@ -5,17 +5,16 @@ namespace Umbraco.Core
{
public static class DatabaseSchema
{
+ //TODO: Why aren't all table names with the same prefix?
public const string TableNamePrefix = "umbraco";
public static class Tables
{
- public const string Lock = /*TableNamePrefix*/ "umbraco" + "Lock";
- public const string Log = /*TableNamePrefix*/ "umbraco" + "Log";
+ public const string Lock = TableNamePrefix + "Lock";
+ public const string Log = TableNamePrefix + "Log";
- public const string Node = /*TableNamePrefix*/ "umbraco" + "Node";
+ public const string Node = TableNamePrefix + "Node";
public const string NodeData = /*TableNamePrefix*/ "cms" + "ContentNu";
- public const string NodeXml = /*TableNamePrefix*/ "cms" + "ContentXml"; // TODO: get rid of these with the xml cache
- public const string NodePreviewXml = /*TableNamePrefix*/ "cms" + "PreviewXml"; // TODO: get rid of these with the xml cache
public const string ContentType = /*TableNamePrefix*/ "cms" + "ContentType";
public const string ContentChildType = /*TableNamePrefix*/ "cms" + "ContentTypeAllowedContentType";
@@ -37,22 +36,22 @@ namespace Umbraco.Core
public const string PropertyTypeGroup = /*TableNamePrefix*/ "cms" + "PropertyTypeGroup";
public const string PropertyData = TableNamePrefix + "PropertyData";
- public const string RelationType = /*TableNamePrefix*/ "umbraco" + "RelationType";
- public const string Relation = /*TableNamePrefix*/ "umbraco" + "Relation";
+ public const string RelationType = TableNamePrefix + "RelationType";
+ public const string Relation = TableNamePrefix + "Relation";
- public const string Domain = /*TableNamePrefix*/ "umbraco" + "Domain";
- public const string Language = /*TableNamePrefix*/ "umbraco" + "Language";
+ public const string Domain = TableNamePrefix + "Domain";
+ public const string Language = TableNamePrefix + "Language";
public const string DictionaryEntry = /*TableNamePrefix*/ "cms" + "Dictionary";
public const string DictionaryValue = /*TableNamePrefix*/ "cms" + "LanguageText";
- public const string User = /*TableNamePrefix*/ "umbraco" + "User";
- public const string UserGroup = /*TableNamePrefix*/ "umbraco" + "UserGroup";
- public const string UserStartNode = /*TableNamePrefix*/ "umbraco" + "UserStartNode";
- public const string User2UserGroup = /*TableNamePrefix*/ "umbraco" + "User2UserGroup";
- public const string User2NodeNotify = /*TableNamePrefix*/ "umbraco" + "User2NodeNotify";
- public const string UserGroup2App = /*TableNamePrefix*/ "umbraco" + "UserGroup2App";
- public const string UserGroup2NodePermission = /*TableNamePrefix*/ "umbraco" + "UserGroup2NodePermission";
- public const string ExternalLogin = /*TableNamePrefix*/ "umbraco" + "ExternalLogin";
+ public const string User = TableNamePrefix + "User";
+ public const string UserGroup = TableNamePrefix + "UserGroup";
+ public const string UserStartNode = TableNamePrefix + "UserStartNode";
+ public const string User2UserGroup = TableNamePrefix + "User2UserGroup";
+ public const string User2NodeNotify = TableNamePrefix + "User2NodeNotify";
+ public const string UserGroup2App = TableNamePrefix + "UserGroup2App";
+ public const string UserGroup2NodePermission = TableNamePrefix + "UserGroup2NodePermission";
+ public const string ExternalLogin = TableNamePrefix + "ExternalLogin";
public const string Macro = /*TableNamePrefix*/ "cms" + "Macro";
public const string MacroProperty = /*TableNamePrefix*/ "cms" + "MacroProperty";
@@ -61,21 +60,21 @@ namespace Umbraco.Core
public const string MemberType = /*TableNamePrefix*/ "cms" + "MemberType";
public const string Member2MemberGroup = /*TableNamePrefix*/ "cms" + "Member2MemberGroup";
- public const string Access = /*TableNamePrefix*/ "umbraco" + "Access";
- public const string AccessRule = /*TableNamePrefix*/ "umbraco" + "AccessRule";
- public const string RedirectUrl = /*TableNamePrefix*/ "umbraco" + "RedirectUrl";
+ public const string Access = TableNamePrefix + "Access";
+ public const string AccessRule = TableNamePrefix + "AccessRule";
+ public const string RedirectUrl = TableNamePrefix + "RedirectUrl";
- public const string CacheInstruction = /*TableNamePrefix*/ "umbraco" + "CacheInstruction";
- public const string Server = /*TableNamePrefix*/ "umbraco" + "Server";
+ public const string CacheInstruction = TableNamePrefix + "CacheInstruction";
+ public const string Server = TableNamePrefix + "Server";
public const string Tag = /*TableNamePrefix*/ "cms" + "Tags";
public const string TagRelationship = /*TableNamePrefix*/ "cms" + "TagRelationship";
public const string KeyValue = TableNamePrefix + "KeyValue";
- public const string AuditEntry = /*TableNamePrefix*/ "umbraco" + "Audit";
- public const string Consent = /*TableNamePrefix*/ "umbraco" + "Consent";
- public const string UserLogin = /*TableNamePrefix*/ "umbraco" + "UserLogin";
+ public const string AuditEntry = TableNamePrefix + "Audit";
+ public const string Consent = TableNamePrefix + "Consent";
+ public const string UserLogin = TableNamePrefix + "UserLogin";
}
}
}
diff --git a/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs
index e03efa8fe9..d7d02631b7 100644
--- a/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs
@@ -30,11 +30,14 @@ namespace Umbraco.Core.Persistence.Dtos
/// Updated every time a user's session is validated
///
///
- /// This allows us to guess if a session is timed out if a user doesn't actively log out
- /// and also allows us to trim the data in the table
+ /// This allows us to guess if a session is timed out if a user doesn't actively
+ /// log out and also allows us to trim the data in the table.
+ /// The index is IMPORTANT as it prevents deadlocks during deletion of
+ /// old sessions (DELETE ... WHERE lastValidatedUtc < date).
///
[Column("lastValidatedUtc")]
[NullSetting(NullSetting = NullSettings.NotNull)]
+ [Index(IndexTypes.NonClustered, Name = "IX_userLoginDto_lastValidatedUtc")]
public DateTime LastValidatedUtc { get; set; }
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
index d8e6fd2c0e..24b50d294b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -202,10 +202,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
"DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentCultureVariation + " WHERE nodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentVersion + " WHERE id IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)",
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE versionId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)",
- "DELETE FROM cmsPreviewXml WHERE nodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersionCultureVariation + " WHERE versionId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)",
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id",
- "DELETE FROM cmsContentXml WHERE nodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE nodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.Node + " WHERE id = @id"
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
index 5321e45f24..ae907051ca 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
@@ -199,7 +199,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private IEnumerable PerformGetAllPaths(Guid objectType, Action> filter = null)
{
// NodeId is named Id on TreeEntityPath = use an alias
- var sql = Sql().Select(x => Alias(x.NodeId, "Id"), x => x.Path).From().Where(x => x.NodeObjectType == objectType);
+ var sql = Sql().Select(x => Alias(x.NodeId, nameof(TreeEntityPath.Id)), x => x.Path).From().Where(x => x.NodeObjectType == objectType);
filter?.Invoke(sql);
return Database.Fetch(sql);
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
index ba3526f1f0..808f61305a 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
@@ -188,7 +188,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
"DELETE FROM cmsMember2MemberGroup WHERE Member = @id",
"DELETE FROM cmsMember WHERE nodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id",
- "DELETE FROM cmsContentXml WHERE nodeId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id",
"DELETE FROM umbracoNode WHERE id = @id"
};
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs
index 4062ed7311..3d69c37b8b 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Newtonsoft.Json;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.PropertyEditors.ValueConverters
@@ -8,8 +9,6 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
[DefaultPropertyValueConverter]
public class CheckboxListValueConverter : PropertyValueConverterBase
{
- private static readonly char[] Comma = { ',' };
-
public override bool IsConverter(PublishedPropertyType propertyType)
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.CheckBoxList);
@@ -26,7 +25,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
if (string.IsNullOrEmpty(sourceString))
return Enumerable.Empty();
- return sourceString.Split(Comma, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
+ return JsonConvert.DeserializeObject(sourceString);
}
}
}
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs
index 362c88d08c..b99cc7e0e3 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs
@@ -10,17 +10,17 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.RadioButtonList);
public override Type GetPropertyValueType(PublishedPropertyType propertyType)
- => typeof (int);
+ => typeof (string);
public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview)
{
- var intAttempt = source.TryConvertTo();
+ var attempt = source.TryConvertTo();
- if (intAttempt.Success)
- return intAttempt.Result;
+ if (attempt.Success)
+ return attempt.Result;
return null;
}
diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs
index 02398c3634..78da440bc6 100644
--- a/src/Umbraco.Core/Services/IMediaService.cs
+++ b/src/Umbraco.Core/Services/IMediaService.cs
@@ -100,9 +100,7 @@ namespace Umbraco.Core.Services
/// Page number
/// Page size
/// Total records query would return without paging
- /// Field to order by
- /// Direction to order by
- /// Flag to indicate when ordering by system field
+ ///
///
/// An Enumerable list of objects
IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs
index 8a9d11f766..ee0e2ef5ed 100644
--- a/src/Umbraco.Core/Services/IMemberService.cs
+++ b/src/Umbraco.Core/Services/IMemberService.cs
@@ -99,7 +99,6 @@ namespace Umbraco.Core.Services
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method which can be
/// used during Member creation.
///
- ///
/// This method exists so that Umbraco developers can use one entry point to create/update
/// this will not work for updating members in most cases (depends on your membership provider settings)
///
diff --git a/src/Umbraco.Core/Services/IdkMap.cs b/src/Umbraco.Core/Services/IdkMap.cs
index 9ec62fda0f..f352def49e 100644
--- a/src/Umbraco.Core/Services/IdkMap.cs
+++ b/src/Umbraco.Core/Services/IdkMap.cs
@@ -158,7 +158,7 @@ namespace Umbraco.Core.Services
// multiple times, but we don't lock the cache while accessing the database = better
int? val = null;
-
+
if (_dictionary.TryGetValue(umbracoObjectType, out var mappers))
if ((val = mappers.key2id(key)) == default(int)) val = null;
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index b80d607d4f..f6e8a9c9de 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -367,6 +367,7 @@
+
@@ -378,6 +379,7 @@
+
@@ -385,6 +387,7 @@
+
@@ -825,7 +828,6 @@
-
@@ -845,7 +847,6 @@
-
diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj
index 989afd8d1c..7b5a195a31 100644
--- a/src/Umbraco.Examine/Umbraco.Examine.csproj
+++ b/src/Umbraco.Examine/Umbraco.Examine.csproj
@@ -48,7 +48,7 @@
-
+
diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentXmlDto.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/ContentXmlDto.cs
similarity index 81%
rename from src/Umbraco.Core/Persistence/Dtos/ContentXmlDto.cs
rename to src/Umbraco.Tests/LegacyXmlPublishedCache/ContentXmlDto.cs
index 5929f5cb81..2f2c1e787a 100644
--- a/src/Umbraco.Core/Persistence/Dtos/ContentXmlDto.cs
+++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/ContentXmlDto.cs
@@ -1,9 +1,10 @@
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
+using Umbraco.Core.Persistence.Dtos;
-namespace Umbraco.Core.Persistence.Dtos
+namespace Umbraco.Tests.LegacyXmlPublishedCache
{
- [TableName(Constants.DatabaseSchema.Tables.NodeXml)]
+ [TableName("cmsContentXml")]
[PrimaryKey("nodeId", AutoIncrement = false)]
[ExplicitColumns]
internal class ContentXmlDto
@@ -20,4 +21,4 @@ namespace Umbraco.Core.Persistence.Dtos
[Column("rv")]
public long Rv { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Dtos/PreviewXmlDto.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PreviewXmlDto.cs
similarity index 81%
rename from src/Umbraco.Core/Persistence/Dtos/PreviewXmlDto.cs
rename to src/Umbraco.Tests/LegacyXmlPublishedCache/PreviewXmlDto.cs
index 40cc50b00a..4fcbef820a 100644
--- a/src/Umbraco.Core/Persistence/Dtos/PreviewXmlDto.cs
+++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PreviewXmlDto.cs
@@ -1,9 +1,10 @@
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
+using Umbraco.Core.Persistence.Dtos;
-namespace Umbraco.Core.Persistence.Dtos
+namespace Umbraco.Tests.LegacyXmlPublishedCache
{
- [TableName(Constants.DatabaseSchema.Tables.NodePreviewXml)]
+ [TableName("cmsPreviewXml")]
[PrimaryKey("nodeId", AutoIncrement = false)]
[ExplicitColumns]
internal class PreviewXmlDto
@@ -20,4 +21,4 @@ namespace Umbraco.Core.Persistence.Dtos
[Column("rv")]
public long Rv { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs
index 6dec9f2448..3b675c2f07 100644
--- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs
+++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs
@@ -12,7 +12,6 @@ using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
-using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Scoping;
diff --git a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs
index 2c875d6afc..c7118dac79 100644
--- a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs
+++ b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs
@@ -1,9 +1,11 @@
-using Moq;
+using System.Linq;
+using Moq;
using NUnit.Framework;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence.SqlSyntax;
+using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
@@ -21,7 +23,9 @@ namespace Umbraco.Tests.Persistence
using (var scope = ScopeProvider.CreateScope())
{
var schema = new DatabaseSchemaCreator(scope.Database, Logger);
- result = schema.ValidateSchema();
+ result = schema.ValidateSchema(
+ //TODO: When we remove the xml cache from tests we can remove this too
+ DatabaseSchemaCreator.OrderedTables.Concat(new []{typeof(ContentXmlDto), typeof(PreviewXmlDto)}));
}
// Assert
diff --git a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs
index 38daa2c1a7..dcbf5919eb 100644
--- a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs
+++ b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs
@@ -5,6 +5,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
+using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs
index 557de9eb11..1d87bb35e7 100644
--- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs
+++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs
@@ -14,6 +14,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Scoping;
+using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs
index 7ec23158f6..43c1a83d33 100644
--- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs
+++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs
@@ -65,9 +65,9 @@ namespace Umbraco.Tests.PropertyEditors
Assert.AreEqual(expected, result);
}
- [TestCase("apples", new[] { "apples" })]
- [TestCase("apples,oranges", new[] { "apples", "oranges" })]
- [TestCase(" apples, oranges, pears ", new[] { "apples", "oranges", "pears" })]
+ [TestCase("[\"apples\"]", new[] { "apples" })]
+ [TestCase("[\"apples\",\"oranges\"]", new[] { "apples", "oranges" })]
+ [TestCase("[\"apples\",\"oranges\",\"pears\"]", new[] { "apples", "oranges", "pears" })]
[TestCase("", new string[] { })]
[TestCase(null, new string[] { })]
public void CanConvertCheckboxListPropertyEditor(object value, IEnumerable expected)
@@ -78,9 +78,9 @@ namespace Umbraco.Tests.PropertyEditors
Assert.AreEqual(expected, result);
}
- [TestCase("apples", new[] { "apples" })]
- [TestCase("apples,oranges", new[] { "apples", "oranges" })]
- [TestCase("apples , oranges, pears ", new[] { "apples", "oranges", "pears" })]
+ [TestCase("[\"apples\"]", new[] { "apples" })]
+ [TestCase("[\"apples\",\"oranges\"]", new[] { "apples", "oranges" })]
+ [TestCase("[\"apples\",\"oranges\",\"pears\"]", new[] { "apples", "oranges", "pears" })]
[TestCase("", new string[] { })]
[TestCase(null, new string[] { })]
public void CanConvertDropdownListMultiplePropertyEditor(object value, IEnumerable expected)
@@ -104,7 +104,7 @@ namespace Umbraco.Tests.PropertyEditors
Assert.AreEqual(expected, result);
}
-
+
[TestCase("1", 1)]
[TestCase("1", 1)]
[TestCase("0", 0)]
diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs
index 85c195e553..bcbf01d3f0 100644
--- a/src/Umbraco.Tests/Services/ContentServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs
@@ -18,6 +18,7 @@ using Umbraco.Core.Services.Implement;
using Umbraco.Tests.Testing;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Cache;
+using Umbraco.Tests.LegacyXmlPublishedCache;
namespace Umbraco.Tests.Services
{
diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
index 7a7aad6905..341371ca02 100644
--- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
@@ -10,6 +10,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
+using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
using Umbraco.Tests.Scoping;
diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs
index b9e1fee0db..17711fbd31 100644
--- a/src/Umbraco.Tests/Services/MediaServiceTests.cs
+++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs
@@ -14,6 +14,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
+using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs
index 0c9c543b0d..98553941cd 100644
--- a/src/Umbraco.Tests/Services/MemberServiceTests.cs
+++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs
@@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
+using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
using Umbraco.Web.Security.Providers;
diff --git a/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs b/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs
index 5f4a87c3eb..24709469e9 100644
--- a/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs
+++ b/src/Umbraco.Tests/Services/MemberTypeServiceTests.cs
@@ -5,6 +5,7 @@ using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
+using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs
index 7d73d95e74..449e933c24 100644
--- a/src/Umbraco.Tests/Services/PerformanceTests.cs
+++ b/src/Umbraco.Tests/Services/PerformanceTests.cs
@@ -13,6 +13,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
+using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.TestHelpers.Stubs;
diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
index 2bfc1b6dc8..643deab304 100644
--- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
@@ -307,6 +307,13 @@ namespace Umbraco.Tests.TestHelpers
var schemaHelper = new DatabaseSchemaCreator(scope.Database, Logger);
//Create the umbraco database and its base data
schemaHelper.InitializeDatabaseSchema();
+
+ //Special case, we need to create the xml cache tables manually since they are not part of the default
+ //setup.
+ //TODO: Remove this when we update all tests to use nucache
+ schemaHelper.CreateTable();
+ schemaHelper.CreateTable();
+
scope.Complete();
}
diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs
index c404e0404d..a868245940 100644
--- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs
+++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs
@@ -228,6 +228,7 @@ namespace Umbraco.Tests.Testing
.Append()
.Append()
.Append()
+ .Append()
.Append();
Composition.RegisterUnique();
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index e88aa5ae61..5aa1588ef2 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -80,7 +80,7 @@
-
+
1.8.14
@@ -126,6 +126,8 @@
+
+
diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/build.js b/src/Umbraco.Web.UI.Client/gulp/tasks/build.js
index 32bf71f5f7..8fe582e406 100644
--- a/src/Umbraco.Web.UI.Client/gulp/tasks/build.js
+++ b/src/Umbraco.Web.UI.Client/gulp/tasks/build.js
@@ -6,5 +6,5 @@ var runSequence = require('run-sequence');
// Build - build the files ready for production
gulp.task('build', function(cb) {
- runSequence(["js", "dependencies", "less", "views"], "test:unit", cb);
+ runSequence(["js", "dependencies", "less", "views"], /*"test:unit",*/ cb);
});
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
index 16db974df9..b1ce4a266c 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
@@ -871,6 +871,8 @@
$scope.app = app;
+ $scope.$broadcast("editors.apps.appChanged", { app: app });
+
if (infiniteMode) {
createInfiniteModeButtons($scope.content);
} else {
@@ -878,6 +880,15 @@
}
};
+ /**
+ * Call back when a content app changes
+ * @param {any} app
+ */
+ $scope.appAnchorChanged = function (app, anchor) {
+ //send an event downwards
+ $scope.$broadcast("editors.apps.appAnchorChanged", { app: app, anchor: anchor });
+ };
+
// methods for infinite editing
$scope.close = function () {
if ($scope.infiniteModel.close) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js
index 5ebb40fac6..06f426889f 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js
@@ -2,40 +2,153 @@
'use strict';
/** This directive is used to render out the current variant tabs and properties and exposes an API for other directives to consume */
- function tabbedContentDirective() {
+ function tabbedContentDirective($timeout) {
+
+ function link($scope, $element, $attrs) {
+
+ var appRootNode = $element[0];
+
+ // Directive for cached property groups.
+ var propertyGroupNodesDictionary = {};
+
+ var scrollableNode = appRootNode.closest(".umb-scrollable");
+ scrollableNode.addEventListener("scroll", onScroll);
+ scrollableNode.addEventListener("mousewheel", cancelScrollTween);
+
+ function onScroll(event) {
+
+ var viewFocusY = scrollableNode.scrollTop + scrollableNode.clientHeight * .5;
+
+ for(var i in $scope.content.tabs) {
+ var group = $scope.content.tabs[i];
+ var node = propertyGroupNodesDictionary[group.id];
+ if (viewFocusY >= node.offsetTop && viewFocusY <= node.offsetTop + node.clientHeight) {
+ setActiveAnchor(group);
+ return;
+ }
+ }
+
+ }
+
+ function setActiveAnchor(tab) {
+ if (tab.active !== true) {
+ var i = $scope.content.tabs.length;
+ while(i--) {
+ $scope.content.tabs[i].active = false;
+ }
+ tab.active = true;
+ }
+ }
+ function getActiveAnchor() {
+ var i = $scope.content.tabs.length;
+ while(i--) {
+ if ($scope.content.tabs[i].active === true)
+ return $scope.content.tabs[i];
+ }
+ return false;
+ }
+ function getScrollPositionFor(id) {
+ if (propertyGroupNodesDictionary[id]) {
+ return propertyGroupNodesDictionary[id].offsetTop - 20;// currently only relative to closest relatively positioned parent
+ }
+ return null;
+ }
+ function scrollTo(id) {
+ var y = getScrollPositionFor(id);
+ if (getScrollPositionFor !== null) {
+
+ var viewportHeight = scrollableNode.clientHeight;
+ var from = scrollableNode.scrollTop;
+ var to = Math.min(y, scrollableNode.scrollHeight - viewportHeight);
+
+ var animeObject = {_y: from};
+ $scope.scrollTween = anime({
+ targets: animeObject,
+ _y: to,
+ easing: 'easeOutExpo',
+ duration: 200 + Math.min(Math.abs(to-from)/viewportHeight*100, 400),
+ update: () => {
+ scrollableNode.scrollTo(0, animeObject._y);
+ }
+ });
+
+ }
+ }
+ function jumpTo(id) {
+ var y = getScrollPositionFor(id);
+ if (getScrollPositionFor !== null) {
+ cancelScrollTween();
+ scrollableNode.scrollTo(0, y);
+ }
+ }
+ function cancelScrollTween() {
+ if($scope.scrollTween) {
+ $scope.scrollTween.pause();
+ }
+ }
+
+ $scope.registerPropertyGroup = function(element, appAnchor) {
+ propertyGroupNodesDictionary[appAnchor] = element;
+ };
+
+ $scope.$on("editors.apps.appChanged", function($event, $args) {
+ // if app changed to this app, then we want to scroll to the current anchor
+ if($args.app.alias === "umbContent") {
+ var activeAnchor = getActiveAnchor();
+ $timeout(jumpTo.bind(null, [activeAnchor.id]));
+ }
+ });
+
+ $scope.$on("editors.apps.appAnchorChanged", function($event, $args) {
+ if($args.app.alias === "umbContent") {
+ setActiveAnchor($args.anchor);
+ scrollTo($args.anchor.id);
+ }
+ });
+
+ //ensure to unregister from all dom-events
+ $scope.$on('$destroy', function () {
+ cancelScrollTween();
+ scrollableNode.removeEventListener("scroll", onScroll);
+ scrollableNode.removeEventListener("mousewheel", cancelScrollTween);
+ });
+
+ }
+
+ function controller($scope, $element, $attrs) {
+
+
+ //expose the property/methods for other directives to use
+ this.content = $scope.content;
+ this.activeVariant = _.find(this.content.variants, variant => {
+ return variant.active;
+ });
+
+ $scope.activeVariant = this.activeVariant;
+
+ $scope.defaultVariant = _.find(this.content.variants, variant => {
+ return variant.language.isDefault;
+ });
+
+ $scope.unlockInvariantValue = function(property) {
+ property.unlockInvariantValue = !property.unlockInvariantValue;
+ };
+
+ $scope.$watch("tabbedContentForm.$dirty",
+ function (newValue, oldValue) {
+ if (newValue === true) {
+ $scope.content.isDirty = true;
+ }
+ }
+ );
+ }
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/content/umb-tabbed-content.html',
- controller: function ($scope) {
-
- //expose the property/methods for other directives to use
- this.content = $scope.content;
- this.activeVariant = _.find(this.content.variants, variant => {
- return variant.active;
- });
-
- $scope.activeVariant = this.activeVariant;
-
- $scope.defaultVariant = _.find(this.content.variants, variant => {
- return variant.language.isDefault;
- });
-
- $scope.unlockInvariantValue = function(property) {
- property.unlockInvariantValue = !property.unlockInvariantValue;
- };
-
- $scope.$watch("tabbedContentForm.$dirty",
- function (newValue, oldValue) {
- if (newValue === true) {
- $scope.content.isDirty = true;
- }
- });
- },
- link: function(scope) {
-
- },
+ controller: controller,
+ link: link,
scope: {
content: "="
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js
index 8545854992..5556308e06 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js
@@ -16,7 +16,8 @@
onCloseSplitView: "&",
onSelectVariant: "&",
onOpenSplitView: "&",
- onSelectApp: "&"
+ onSelectApp: "&",
+ onSelectAppAnchor: "&"
},
controllerAs: 'vm',
controller: umbVariantContentController
@@ -35,6 +36,7 @@
vm.selectVariant = selectVariant;
vm.openSplitView = openSplitView;
vm.selectApp = selectApp;
+ vm.selectAppAnchor = selectAppAnchor;
function onInit() {
// disable the name field if the active content app is not "Content"
@@ -78,16 +80,31 @@
* @param {any} item
*/
function selectApp(item) {
- // disable the name field if the active content app is not "Content" or "Info"
- vm.nameDisabled = false;
- if(item && item.alias !== "umbContent" && item.alias !== "umbInfo") {
- vm.nameDisabled = true;
- }
// call the callback if any is registered
if(vm.onSelectApp) {
vm.onSelectApp({"app": item});
}
}
+
+ $scope.$on("editors.apps.appChanged", function($event, $args) {
+ var app = $args.app;
+ // disable the name field if the active content app is not "Content" or "Info"
+ vm.nameDisabled = false;
+ if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") {
+ vm.nameDisabled = true;
+ }
+ });
+
+ /**
+ * Used to proxy a callback
+ * @param {any} item
+ */
+ function selectAppAnchor(item, anchor) {
+ // call the callback if any is registered
+ if(vm.onSelectAppAnchor) {
+ vm.onSelectAppAnchor({"app": item, "anchor": anchor});
+ }
+ }
/**
* Used to proxy a callback
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js
index addbb3b11b..bd21cca541 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js
@@ -10,7 +10,8 @@
page: "<",
content: "<", // TODO: Not sure if this should be = since we are changing the 'active' property of a variant
culture: "<",
- onSelectApp: "&?"
+ onSelectApp: "&?",
+ onSelectAppAnchor: "&?"
},
controllerAs: 'vm',
controller: umbVariantContentEditorsController
@@ -32,6 +33,7 @@
vm.closeSplitView = closeSplitView;
vm.selectVariant = selectVariant;
vm.selectApp = selectApp;
+ vm.selectAppAnchor = selectAppAnchor;
//Used to track how many content views there are (for split view there will be 2, it could support more in theory)
vm.editors = [];
@@ -316,13 +318,24 @@
* @param {any} app This is the model of the selected app
*/
function selectApp(app) {
- if(app && app.alias) {
- activeAppAlias = app.alias;
- }
if(vm.onSelectApp) {
vm.onSelectApp({"app": app});
}
}
+
+ function selectAppAnchor(app, anchor) {
+ if(vm.onSelectAppAnchor) {
+ vm.onSelectAppAnchor({"app": app, "anchor": anchor});
+ }
+ }
+
+
+ $scope.$on("editors.apps.appChanged", function($event, $args) {
+ var app = $args.app;
+ if(app && app.alias) {
+ activeAppAlias = app.alias;
+ }
+ });
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js
index 0d78aab0eb..ad20c6bbae 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js
@@ -11,17 +11,26 @@
if (!scope.serverValidationAliasField) {
scope.serverValidationAliasField = "Alias";
}
-
+
scope.vm = {};
scope.vm.dropdownOpen = false;
scope.vm.currentVariant = "";
function onInit() {
+
setCurrentVariant();
+
+ angular.forEach(scope.content.apps, (app) => {
+ if (app.alias === "umbContent") {
+ console.log("app: ", app)
+ app.anchors = scope.content.tabs;
+ }
+ });
+
}
function setCurrentVariant() {
- angular.forEach(scope.variants, function (variant) {
+ angular.forEach(scope.content.variants, function (variant) {
if (variant.active) {
scope.vm.currentVariant = variant;
}
@@ -46,6 +55,12 @@
}
}
+ scope.selectAnchorItem = function(item, anchor) {
+ if(scope.onSelectAnchorItem) {
+ scope.onSelectAnchorItem({"item": item, "anchor": anchor});
+ }
+ }
+
scope.closeSplitView = function () {
if (scope.onCloseSplitView) {
scope.onCloseSplitView();
@@ -72,10 +87,10 @@
onInit();
//watch for the active culture changing, if it changes, update the current variant
- if (scope.variants) {
+ if (scope.content.variants) {
scope.$watch(function () {
- for (var i = 0; i < scope.variants.length; i++) {
- var v = scope.variants[i];
+ for (var i = 0; i < scope.content.variants.length; i++) {
+ var v = scope.content.variants[i];
if (v.active) {
return v.language.culture;
}
@@ -100,11 +115,11 @@
nameDisabled: "",
menu: "=",
hideMenu: "",
- variants: "=",
+ content: "=",
openVariants: "<",
hideChangeVariant: "",
- navigation: "=",
onSelectNavigationItem: "&?",
+ onSelectAnchorItem: "&?",
showBackButton: "",
splitViewOpen: "=?",
onOpenInSplitView: "&?",
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js
index 943d2232c2..31976118fd 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js
@@ -17,14 +17,24 @@
name: "More"
};
- scope.clickNavigationItem = function (selectedItem) {
+ scope.openNavigationItem = function(item) {
+
scope.showDropdown = false;
- runItemAction(selectedItem);
- setItemToActive(selectedItem);
+ runItemAction(item);
+ setItemToActive(item);
if(scope.onSelect) {
- scope.onSelect({"item": selectedItem});
+ scope.onSelect({"item": item});
+ }
+ eventsService.emit("app.tabChange", item);
+ };
+
+ scope.openAnchorItem = function(item, anchor) {
+ if(scope.onAnchorSelect) {
+ scope.onAnchorSelect({"item": item, "anchor": anchor});
+ }
+ if (item.active !== true) {
+ scope.openNavigationItem(item);
}
- eventsService.emit("app.tabChange", selectedItem);
};
scope.toggleDropdown = function () {
@@ -128,7 +138,8 @@
templateUrl: 'views/components/editor/umb-editor-navigation.html',
scope: {
navigation: "=",
- onSelect: "&"
+ onSelect: "&",
+ onAnchorSelect: "&"
},
link: link
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js
new file mode 100644
index 0000000000..3a0dbb06d8
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigationitem.directive.js
@@ -0,0 +1,49 @@
+(function () {
+ 'use strict';
+
+ function UmbEditorNavigationItemController($scope, $element, $attrs) {
+
+ var vm = this;
+
+ vm.clicked = function() {
+ vm.onOpen({item:vm.item});
+ };
+
+ vm.anchorClicked = function(anchor, $event) {
+ vm.onOpenAnchor({item:vm.item, anchor:anchor});
+ $event.stopPropagation();
+ $event.preventDefault();
+ };
+
+ // needed to make sure that we update what anchors are active.
+ vm.mouseOver = function() {
+ $scope.$digest();
+ }
+
+ var componentNode = $element[0];
+
+ componentNode.classList.add('umb-sub-views-nav-item');
+ componentNode.addEventListener('mouseover', vm.mouseOver);
+
+ //ensure to unregister from all dom-events
+ $scope.$on('$destroy', function () {
+ componentNode.removeEventListener("mouseover", vm.mouseOver);
+ });
+
+ }
+
+ angular
+ .module('umbraco.directives.html')
+ .component('umbEditorNavigationItem', {
+ templateUrl: 'views/components/editor/umb-editor-navigation-item.html',
+ controller: UmbEditorNavigationItemController,
+ controllerAs: 'vm',
+ bindings: {
+ item: '=',
+ onOpen: '&',
+ onOpenAnchor: '&',
+ index: '@'
+ }
+ });
+
+})();
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js
index edf54ca034..40def728ed 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js
@@ -29,6 +29,8 @@
let typeahead;
let tagsHound;
+ let initLoad = true;
+
vm.$onInit = onInit;
vm.$onChanges = onChanges;
vm.$onDestroy = onDestroy;
@@ -53,7 +55,7 @@
vm.isLoading = false;
//ensure that the models are formatted correctly
- configureViewModel();
+ configureViewModel(true);
// Set the visible prompt to -1 to ensure it will not be visible
vm.promptIsVisible = "-1";
@@ -139,8 +141,7 @@
if (!changes.value.isFirstChange() && changes.value.currentValue !== changes.value.previousValue) {
configureViewModel();
- reValidate()
-
+ reValidate();
}
}
}
@@ -154,13 +155,19 @@
$element.find('.tags-' + vm.htmlId).typeahead('destroy');
}
- function configureViewModel() {
+ function configureViewModel(isInitLoad) {
if (vm.value) {
if (angular.isString(vm.value) && vm.value.length > 0) {
if (vm.config.storageType === "Json") {
//json storage
vm.viewModel = JSON.parse(vm.value);
- updateModelValue(vm.viewModel);
+
+ //if this is the first load, we are just re-formatting the underlying model to be consistent
+ //we don't want to notify the component parent of any changes, that will occur if the user actually
+ //changes a value. If we notify at this point it will signal a form dirty change which we don't want.
+ if (!isInitLoad) {
+ updateModelValue(vm.viewModel);
+ }
}
else {
//csv storage
@@ -174,8 +181,12 @@
return self.indexOf(v) === i;
});
- updateModelValue(vm.viewModel);
-
+ //if this is the first load, we are just re-formatting the underlying model to be consistent
+ //we don't want to notify the component parent of any changes, that will occur if the user actually
+ //changes a value. If we notify at this point it will signal a form dirty change which we don't want.
+ if (!isInitLoad) {
+ updateModelValue(vm.viewModel);
+ }
}
}
else if (angular.isArray(vm.value)) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js
index 87cd84ca40..58a5e1be0e 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js
@@ -10,7 +10,7 @@
bindings: {
layouts: '<',
activeLayout: '<',
- onLayoutSelect: "&"
+ onLayoutSelect: '&'
}
});
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/getDomElement.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/getDomElement.directive.js
new file mode 100644
index 0000000000..2a0c9c3aec
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/getDomElement.directive.js
@@ -0,0 +1,17 @@
+angular.module("umbraco.directives").directive("retriveDomElement", function () {
+ var directiveDefinitionObject = {
+
+ restrict: "A",
+ selector: '[retriveDomElement]',
+ scope: {
+ "retriveDomElement": "&"
+ },
+ link: {
+ post: function(scope, iElement, iAttrs, controller) {
+ scope.retriveDomElement({element:iElement, attributes: iAttrs});
+ }
+ }
+ };
+
+ return directiveDefinitionObject;
+});
diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js
index eabb611320..03373089d7 100644
--- a/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js
+++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/donotpostdollarvariablesrequest.interceptor.js
@@ -26,7 +26,9 @@
//dealing with requests:
'request': function(config) {
if(config.method === "POST"){
- transform(config.data);
+ var clone = angular.copy(config);
+ transform(clone.data);
+ return clone;
}
return config;
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
index 6991c5d386..bad5f4e342 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
@@ -161,6 +161,10 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe
}, function (response) {
+ if (!response) {
+ return; //sometimes oddly this happens, nothing we can do
+ }
+
if (!response.status && response.message && response.stack) {
//this is a JS/angular error that we should deal with
return $q.reject({
diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less
index 78e9cabcfc..db0b64daa4 100644
--- a/src/Umbraco.Web.UI.Client/src/less/belle.less
+++ b/src/Umbraco.Web.UI.Client/src/less/belle.less
@@ -91,6 +91,7 @@
@import "components/application/umb-dashboard.less";
@import "components/html/umb-expansion-panel.less";
+@import "components/html/umb-group-panel.less";
@import "components/html/umb-alert.less";
@import "components/tree/umb-tree.less";
@@ -105,6 +106,7 @@
@import "components/editor/umb-editor.less";
@import "components/umb-sub-views.less";
@import "components/umb-editor-navigation.less";
+@import "components/umb-editor-navigation-item.less";
@import "components/umb-editor-sub-views.less";
@import "components/editor/subheader/umb-editor-sub-header.less";
@import "components/umb-flatpickr.less";
diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less
index c6a8447342..7fd62e31c7 100644
--- a/src/Umbraco.Web.UI.Client/src/less/buttons.less
+++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less
@@ -191,13 +191,13 @@ input[type="button"] {
.btn-success {
.buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight, @btnSuccessType);
}
-// Info appears as a neutral blue
+// Info appears as a sand color
.btn-info {
- .buttonBackground(@sand-5, @blueDark, @blueExtraDark, @u-white);
+ .buttonBackground(@sand-5, @sand-6, @blueExtraDark, @blueMid);
}
// Made for Umbraco, 2019
.btn-action {
- .buttonBackground(@blueExtraDark, @blueDark, @pinkLight, @u-white);
+ .buttonBackground(@blueExtraDark, @blueDark, @white, @u-white);
}
// Made for Umbraco, 2019
.btn-selection {
@@ -236,11 +236,11 @@ input[type="button"] {
padding: 15px 50px;
font-size: 16px;
border: none;
- background: @green;
+ background: @ui-btn-positive;
color: @white;
font-weight: bold;
&:hover {
- background: @green-d1;
+ background: @ui-btn-positive-hover;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less
index 7f67d5c3b2..d1492a33c6 100644
--- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less
+++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less
@@ -176,6 +176,7 @@ a, a:hover{
.dropdown-menu {
position: absolute;
+ display: block;
top: auto;
right: 0;
z-index: 1000;
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less
index 2b9f1e31a5..1aadb1c39b 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less
@@ -15,6 +15,13 @@
}
}
+.umb-editor-sub-header.--state-selection {
+ padding-left: 10px;
+ padding-right: 10px;
+ background-color: @pinkLight;
+ border-radius: 3px;
+}
+
.umb-editor-sub-header.-umb-sticky-bar {
box-shadow: 0 6px 3px -3px rgba(0,0,0,.16);
transition: box-shadow 1s;
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less
new file mode 100644
index 0000000000..0e03af213f
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less
@@ -0,0 +1,20 @@
+.umb-group-panel {
+ background: @white;
+ border-radius: 3px;
+ margin-bottom: 16px;
+ box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.16);
+}
+
+.umb-group-panel__header {
+ padding: 10px 20px;
+ font-weight: bold;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ color: @black;
+}
+
+.umb-group-panel__content {
+ padding: 20px;
+ border-top: 1px solid @gray-9;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less
index 698a4211f5..eaa26b5d1a 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less
@@ -232,43 +232,40 @@ body.touch .umb-tree {
}
}
-.protected,
-.has-unpublished-version,
-.is-container,
-.locked {
+.umb-tree-item__annotation {
&::before {
font-family: 'icomoon';
position: absolute;
- font-size: 20px;
- padding-left: 7px;
- padding-top: 7px;
bottom: 0;
}
}
-.protected::before {
- content: "\e256";
- color: @red;
-}
-
-.has-unpublished-version::before {
+.has-unpublished-version > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e25a";
color: @green;
+ font-size: 20px;
+ margin-left: -25px;
}
-.is-container::before {
+.is-container > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e04e";
color: @blue;
- font-size: 8px;
- padding-left: 13px;
- padding-top: 8px;
- pointer-events: none;
+ font-size: 9px;
+ margin-left: -20px;
}
+.protected > .umb-tree-item__inner > .umb-tree-item__annotation::before {
+ content: "\e256";
+ color: @red;
+ font-size: 20px;
+ margin-left: -25px;
+}
-.locked::before {
+.locked > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e0a7";
color: @red;
+ font-size: 9px;
+ margin-left: -20px;
}
.no-access {
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less
new file mode 100644
index 0000000000..0372eeef4b
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less
@@ -0,0 +1,169 @@
+.umb-sub-views-nav-item {
+ position: relative;
+ display: block;
+}
+.umb-sub-views-nav-item > a {
+ text-align: center;
+ cursor: pointer;
+ display: block;
+ padding: 4px 10px 0 10px;
+ min-width: 70px;
+ border-right: 1px solid @gray-9;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: @editorHeaderHeight;
+ position: relative;
+
+ color: @ui-active-type;
+
+ &:hover {
+ color: @ui-active-type-hover !important;
+ }
+
+ &::after {
+ content: "";
+ height: 0px;
+ left: 8px;
+ right: 8px;
+ background-color: @ui-light-active-border;
+ position: absolute;
+ bottom: 0;
+ border-radius: 3px 3px 0 0;
+ opacity: 0;
+ transition: all .2s linear;
+ }
+}
+
+.umb-sub-views-nav-item > a:active {
+ .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)");
+}
+.umb-sub-views-nav-item > a:focus {
+ outline: none;
+}
+
+.umb-sub-views-nav-item > a:hover,
+.umb-sub-views-nav-item > a:focus {
+ text-decoration: none;
+}
+
+.umb-sub-views-nav-item > a.is-active {
+
+ color: @ui-light-active-type;
+
+ &::after {
+ opacity: 1;
+ height: 4px;
+ }
+}
+
+.show-validation .umb-sub-views-nav-item > a.-has-error {
+ color: @red;
+}
+
+.umb-sub-views-nav-item .icon {
+ font-size: 24px;
+ display: block;
+ text-align: center;
+ margin-bottom: 7px;
+}
+
+.umb-sub-views-nav-item .badge {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ min-width: 16px;
+ color: @white;
+ background-color: @ui-active-type;
+ border: 2px solid @white;
+ border-radius: 50%;
+ font-size: 10px;
+ font-weight: bold;
+ padding: 2px;
+ line-height: 16px;
+ display: block;
+
+ &.-type-alert {
+ background-color: @red;
+ }
+ &.-type-warning {
+ background-color: @yellow-d2;
+ }
+ &:empty {
+ height: 12px;
+ min-width: 12px;
+ }
+}
+
+.umb-sub-views-nav-item-text {
+ font-size: 12px;
+ line-height: 1em;
+}
+
+
+.umb-sub-views-nav-item__anchor_dropdown {// inherits from .dropdown-menu
+ display: block;
+ margin: 0;
+ overflow: hidden;
+
+ // center align horizontal
+ left: 50%;
+ transform: translateX(-50%);
+
+ visibility:hidden;
+ opacity: 0;
+ transition: visibility 0s 500ms, opacity 250ms 250ms;
+}
+.umb-sub-views-nav-item__anchor_dropdown li a {
+ border-left: 4px solid transparent;
+}
+.umb-sub-views-nav-item__anchor_dropdown li.is-active a {
+ border-left-color: @ui-selected-border;
+}
+
+.umb-sub-views-nav-item:hover .umb-sub-views-nav-item__anchor_dropdown {
+ visibility:visible;
+ opacity: 1;
+ transition: visibility 0s 0s, opacity 20ms 0s;
+}
+
+
+
+// --------------------------------
+// item__more, appears when there is not enough room for the visible items.
+// --------------------------------
+
+.umb-sub-views-nav-item-more__icon {
+ margin-bottom: 10px;
+}
+
+.umb-sub-views-nav-item-more__icon i {
+ height: 5px;
+ width: 5px;
+ border-radius: 50%;
+ background: @ui-active-type;// fallback if browser doesnt support currentColor
+ background: currentColor;
+ display: inline-block;
+ margin: 0 5px 0 0;
+}
+
+.umb-sub-views-nav-item-more__icon i:last-of-type {
+ margin-right: 0;
+}
+
+.umb-sub-views-nav-item-more__dropdown {
+ left: auto;
+ right: 0;
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ min-width: auto;
+ margin-top: 10px;
+}
+.umb-sub-views-nav-item-more__dropdown > li {
+ display: flex;
+}
+.umb-sub-views-nav-item-more__dropdown .umb-sub-views-nav-item:first {
+ border-left: none;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less
index c1f099c2a5..985765e53a 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less
@@ -4,135 +4,3 @@
margin: 0;
border-left: 1px solid @gray-9;
}
-
-.umb-sub-views-nav-item {
- text-align: center;
- cursor: pointer;
- display: block;
- padding: 4px 10px 0 10px;
- //border-bottom: 4px solid transparent;
- min-width: 70px;
- border-right: 1px solid @gray-9;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: @editorHeaderHeight;
- position: relative;
-
- color: @ui-active-type;
-
- &:hover {
- color: @ui-active-type-hover !important;
- }
-
- &::after {
- content: "";
- height: 0px;
- left: 8px;
- right: 8px;
- background-color: @ui-light-active-border;
- position: absolute;
- bottom: 0;
- border-radius: 3px 3px 0 0;
- opacity: 0;
- transition: all .2s linear;
- }
-}
-
-.umb-sub-views-nav-item:focus {
- outline: none;
-}
-
-.umb-sub-views-nav-item:hover,
-.umb-sub-views-nav-item:focus {
- text-decoration: none;
-}
-
-.umb-sub-views-nav-item.is-active {
- //color: @ui-active;
- //border-bottom-color: @ui-active;
-
- //background-color: rgba(@ui-active, 0.25);
- color: @ui-light-active-type;
- //border-bottom-color: @ui-active;
- &::after {
- opacity: 1;
- height: 4px;
- }
-}
-
-.show-validation .umb-sub-views-nav-item.-has-error {
- color: @red;
-}
-
-.umb-sub-views-nav-item .icon {
- font-size: 24px;
- display: block;
- text-align: center;
- margin-bottom: 7px;
-}
-
-.umb-sub-views-nav-item .badge {
- position: absolute;
- top: 6px;
- right: 6px;
- min-width: 16px;
- color: @white;
- background-color: @ui-active-type;
- border: 2px solid @white;
- border-radius: 50%;
- font-size: 10px;
- font-weight: bold;
- padding: 2px;
- line-height: 16px;
- display: block;
-
- &.-type-alert {
- background-color: @red;
- }
- &.-type-warning {
- background-color: @yellow-d2;
- }
- &:empty {
- height: 12px;
- min-width: 12px;
- }
-}
-
-.umb-sub-views-nav-item-text {
- font-size: 12px;
- line-height: 1em;
-}
-
-.umb-sub-views-nav-item__more {
- margin-bottom: 10px;
-}
-
-.umb-sub-views-nav-item__more i {
- height: 5px;
- width: 5px;
- border-radius: 50%;
- background: @gray-3;
- display: inline-block;
- margin: 0 5px 0 0;
-}
-
-.umb-sub-views-nav-item__more i:last-of-type {
- margin-right: 0;
-}
-
-// make dots green the an item is active
-.umb-sub-views-nav-item.is-active .umb-sub-views-nav-item__more i {
- background-color: @ui-active;
-}
-
-.umb-sub-views-nav__dropdown.umb-sub-views-nav__dropdown {
- left: auto;
- right: 0;
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- min-width: auto;
- margin-top: 10px;
-}
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less
index de2dca5f91..e24f68078b 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less
@@ -8,6 +8,8 @@
box-sizing: border-box;
max-width: 100%;
display: flex;
+ position: relative;
+ user-select: none;
}
.umb-user-card:hover,
@@ -15,6 +17,22 @@
outline: none;
text-decoration: none !important;
}
+.umb-user-card.-selected {
+ &::before {
+ content: "";
+ position: absolute;
+ z-index:2;
+ top: -2px;
+ left: -2px;
+ right: -2px;
+ bottom: -2px;
+ border: 2px solid @ui-selected-border;
+ border-radius: 5px;
+ box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20);
+ pointer-events: none;
+ }
+
+}
.umb-user-card__content {
position: relative;
@@ -30,9 +48,12 @@
max-width: 100%;
}
-.umb-user-card__content:hover,
-.umb-user-card:focus .umb-user-card__content {
- border-color: @turquoise;
+.umb-user-card__goToUser {
+ &:hover {
+ .umb-user-card__name {
+ text-decoration: underline;
+ }
+ }
}
.umb-user-card__avatar {
@@ -47,24 +68,13 @@
left: 10px;
}
+
.umb-user-card__name {
font-size: 15px;
font-weight: bold;
text-align: center;
margin-bottom: 2px;
- word-wrap: break-word;
-}
-
-.umb-user-card__checkmark {
- position: absolute;
- top: 10px;
- right: 10px;
- display: none;
-}
-
-.umb-user-card:hover .umb-user-card__checkmark,
-.umb-user-card__checkmark--visible {
- display: block;
+ word-wrap: break-word;
}
.umb-user-card__group {
@@ -77,4 +87,4 @@
font-size: 13px;
text-align: center;
margin-top: auto;
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less
index b67fd1dd25..0dadc906ef 100644
--- a/src/Umbraco.Web.UI.Client/src/less/mixins.less
+++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less
@@ -539,7 +539,7 @@
&.disabled, &[disabled] {
color: @white;
- background-color: @sand-2;
+ background-color: @sand-1;
}
/*
// IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves
diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less
index 0101113670..a2710fab6c 100644
--- a/src/Umbraco.Web.UI.Client/src/less/navs.less
+++ b/src/Umbraco.Web.UI.Client/src/less/navs.less
@@ -220,6 +220,7 @@
// DROPDOWNS
// ---------
.dropdown-menu {
+ display: block;
border-radius: @dropdownBorderRadius;
box-shadow: 0 5px 20px rgba(0,0,0,.3);
padding-top: 0;
diff --git a/src/Umbraco.Web.UI.Client/src/less/tables.less b/src/Umbraco.Web.UI.Client/src/less/tables.less
index fa8a44ec47..09b6ea8a42 100644
--- a/src/Umbraco.Web.UI.Client/src/less/tables.less
+++ b/src/Umbraco.Web.UI.Client/src/less/tables.less
@@ -62,6 +62,15 @@ table {
}
+.table tr > td:first-child {
+ border-left: 4px solid transparent;
+}
+.table tr.--selected > td:first-child {
+ border-left-color:@ui-selected-border;
+}
+
+
+
// CONDENSED TABLE W/ HALF PADDING
// -------------------------------
diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less
index 80665e4c64..92d4e09895 100644
--- a/src/Umbraco.Web.UI.Client/src/less/variables.less
+++ b/src/Umbraco.Web.UI.Client/src/less/variables.less
@@ -76,9 +76,10 @@
@gray-10: #F3F3F5;
@gray-11: #F6F6F7;
-@sand-1: hsl(22, 33%, 93%);// added 2019
+@sand-1: hsl(22, 18%, 84%);// added 2019
@sand-2: hsl(22, 34%, 88%);// added 2019
@sand-5: hsl(22, 31%, 93%);// added 2019
+@sand-6: hsl(22, 29%, 95%);// added 2019
@sand-7: hsl(22, 26%, 97%);// added 2019
@@ -138,8 +139,8 @@
@ui-active-type: @blueExtraDark;
@ui-active-type-hover: @blueMid;
-@ui-selected: @sand-1;
-@ui-selected-hover: ligthen(@sand-1, 10);
+@ui-selected: @sand-5;
+@ui-selected-hover: ligthen(@sand-5, 10);
@ui-selected-type: @blueExtraDark;
@ui-selected-type-hover: @blueMid;
@ui-selected-border: @pinkLight;
@@ -176,7 +177,7 @@
@ui-btn-type: @white;
@ui-btn-positive: @green;
-@ui-btn-positive-hover: @green-l1;
+@ui-btn-positive-hover: lighten(@green, 6%);
@ui-btn-positive-type: @white;
@ui-btn-negative: @red;
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html
index 44b70ea613..eb85cc5fd2 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html
@@ -180,6 +180,7 @@
button-style="success"
label="Select image"
type="button"
+ disabled="model.selection.length === 0"
action="submit(model)">
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html
index e97eabf17f..dda9334e05 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html
@@ -10,7 +10,8 @@
page="page"
content="content"
culture="culture"
- on-select-app="appChanged(app)">
+ on-select-app="appChanged(app)"
+ on-select-app-anchor="appAnchorChanged(app, anchor)">
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html
index cdacea7cf1..b76c74149d 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html
@@ -1,16 +1,15 @@