diff --git a/.github/BUILD.md b/.github/BUILD.md
index c89a1be460..a9c26f3a9b 100644
--- a/.github/BUILD.md
+++ b/.github/BUILD.md
@@ -1,4 +1,4 @@
-# Umbraco Cms Build
+# Umbraco CMS Build
## Are you sure?
@@ -66,7 +66,7 @@ The Visual Studio object is `null` when Visual Studio has not been detected (eg
* `Path`: Visual Studio installation path (eg some place under `Program Files`)
* `Major`: Visual Studio major version (eg `15` for VS 2017)
* `Minor`: Visual Studio minor version
-* `MsBUild`: the absolute path to the MsBuild executable
+* `MsBuild`: the absolute path to the MsBuild executable
#### GetUmbracoVersion
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index 0e79851c0b..1526c54656 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -29,4 +29,4 @@ Don't rest on your laurels and never accept the status quo. Contribute and give
## Friendly
-Don’t judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and builds long lasting relationships.
\ No newline at end of file
+Don’t judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and build long lasting relationships.
\ No newline at end of file
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 84115b946a..4f5402c512 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -38,7 +38,7 @@ This document gives you a quick overview on how to get started.
### Guidelines for contributions we welcome
-Not all changes are wanted, so on occassion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
+Not all changes are wanted, so on occasion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes.
diff --git a/.github/CONTRIBUTION_GUIDELINES.md b/.github/CONTRIBUTION_GUIDELINES.md
index 7d2afb46bf..0ac35e6897 100644
--- a/.github/CONTRIBUTION_GUIDELINES.md
+++ b/.github/CONTRIBUTION_GUIDELINES.md
@@ -13,7 +13,7 @@ We’re usually able to handle small PRs pretty quickly. A community volunteer w
Umbraco HQ will regularly mark newly created issues on the issue tracker with the `Up for grabs` tag. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
-If you do start working on something, make sure leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
+If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
## Large PRs
New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.).
@@ -30,6 +30,6 @@ It is highly recommended that you speak to the HQ before making large, complex c
### Pull request or package?
-If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and to fix bugs.
+If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs.
Eventually, a package could "graduate" to be included in the CMS.
diff --git a/.github/README.md b/.github/README.md
index bdf9ef9f67..23142186df 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -1,4 +1,4 @@
-# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://pullreminders.com?ref=badge)
+# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://twitter.com/intent/follow?screen_name=umbraco)
Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt
index f0bfb01585..2b79f95c70 100644
--- a/build/NuSpecs/tools/Web.config.install.xdt
+++ b/build/NuSpecs/tools/Web.config.install.xdt
@@ -53,7 +53,7 @@
-
+
@@ -76,7 +76,7 @@
-
+
diff --git a/src/Umbraco.Core/Mapping/UmbracoMapper.cs b/src/Umbraco.Core/Mapping/UmbracoMapper.cs
index e41a40e3d9..e62825101c 100644
--- a/src/Umbraco.Core/Mapping/UmbracoMapper.cs
+++ b/src/Umbraco.Core/Mapping/UmbracoMapper.cs
@@ -343,16 +343,20 @@ namespace Umbraco.Core.Mapping
if (ctor == null) return null;
- if (_ctors.ContainsKey(sourceType))
+ _ctors.AddOrUpdate(sourceType, sourceCtor, (k, v) =>
{
+ // Add missing constructors
foreach (var c in sourceCtor)
{
- if (!_ctors[sourceType].TryGetValue(c.Key, out _))
- _ctors[sourceType].Add(c.Key, c.Value);
- }
- }
- else
- _ctors[sourceType] = sourceCtor;
+ if (!v.ContainsKey(c.Key))
+ {
+ v.Add(c.Key, c.Value);
+ }
+ }
+
+ return v;
+ });
+
return ctor;
}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs
index 7b2daa99ef..95b272dcb4 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs
@@ -74,9 +74,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
.From()
.Where(x => x.NodeId == group.Key)).First();
+ // check for duplicate aliases
+ var aliases = group.Select(x => x.Alias).Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
+ if (aliases.Distinct().Count() != aliases.Length)
+ throw new InvalidOperationException($"Cannot migrate prevalues for datatype id={dataType.NodeId}, editor={dataType.EditorAlias}: duplicate alias.");
+
+ // handle null/empty aliases
+ int index = 0;
+ var dictionary = group.ToDictionary(x => string.IsNullOrWhiteSpace(x.Alias) ? index++.ToString() : x.Alias);
+
// migrate the preValues to configuration
var migrator = _preValueMigrators.GetMigrator(dataType.EditorAlias) ?? new DefaultPreValueMigrator();
- var config = migrator.GetConfiguration(dataType.NodeId, dataType.EditorAlias, group.ToDictionary(x => x.Alias, x => x));
+ var config = migrator.GetConfiguration(dataType.NodeId, dataType.EditorAlias, dictionary);
var json = JsonConvert.SerializeObject(config);
// validate - and kill the migration if it fails
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DefaultPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DefaultPreValueMigrator.cs
index 7112679de2..0c8161c9ef 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DefaultPreValueMigrator.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DefaultPreValueMigrator.cs
@@ -24,8 +24,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
}
// assuming we don't want to fall back to array
- if (aliases.Length != preValuesA.Count || aliases.Any(string.IsNullOrWhiteSpace))
- throw new InvalidOperationException($"Cannot migrate datatype w/ id={dataTypeId} preValues: duplicate or null/empty alias.");
+ if (aliases.Any(string.IsNullOrWhiteSpace))
+ throw new InvalidOperationException($"Cannot migrate prevalues for datatype id={dataTypeId}, editor={editorAlias}: null/empty alias.");
// dictionary-base prevalues
return GetPreValues(preValuesA).ToDictionary(x => x.Alias, GetPreValueValue);
diff --git a/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs b/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs
deleted file mode 100644
index 48edee3c94..0000000000
--- a/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Runtime.CompilerServices;
-
-namespace Umbraco.Core.Persistence
-{
- internal static class DatabaseNodeLockExtensions
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void ValidateDatabase(IUmbracoDatabase database)
- {
- if (database == null)
- throw new ArgumentNullException("database");
- if (database.GetCurrentTransactionIsolationLevel() < IsolationLevel.RepeatableRead)
- throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
- }
-
- // updating a record within a repeatable-read transaction gets an exclusive lock on
- // that record which will be kept until the transaction is ended, effectively locking
- // out all other accesses to that record - thus obtaining an exclusive lock over the
- // protected resources.
- public static void AcquireLockNodeWriteLock(this IUmbracoDatabase database, int nodeId)
- {
- ValidateDatabase(database);
-
- database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id",
- new { @id = nodeId });
- }
-
- // reading a record within a repeatable-read transaction gets a shared lock on
- // that record which will be kept until the transaction is ended, effectively preventing
- // other write accesses to that record - thus obtaining a shared lock over the protected
- // resources.
- public static void AcquireLockNodeReadLock(this IUmbracoDatabase database, int nodeId)
- {
- ValidateDatabase(database);
-
- database.ExecuteScalar("SELECT value FROM umbracoLock WHERE id=@id",
- new { @id = nodeId });
- }
- }
-}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs
index afb419ebd6..3a44cb10b4 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs
@@ -7,5 +7,12 @@ namespace Umbraco.Core.Persistence.Repositories
public interface IDataTypeRepository : IReadWriteQueryRepository
{
IEnumerable> Move(IDataType toMove, EntityContainer container);
+
+ ///
+ /// Returns a dictionary of content type s and the property type aliases that use a
+ ///
+ ///
+ ///
+ IReadOnlyDictionary> FindUsages(int id);
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index f2efb03ba4..6385482686 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -1,4 +1,5 @@
-using System;
+
+using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
@@ -218,6 +219,7 @@ AND umbracoNode.nodeObjectType = @objectType",
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
{
+ CorrectPropertyTypeVariations(entity);
ValidateVariations(entity);
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
@@ -410,26 +412,7 @@ AND umbracoNode.id <> @id",
// note: this only deals with *local* property types, we're dealing w/compositions later below
foreach (var propertyType in entity.PropertyTypes)
{
- if (contentTypeVariationChanging)
- {
- // content type is changing
- switch (newContentTypeVariation)
- {
- case ContentVariation.Nothing: // changing to Nothing
- // all property types must change to Nothing
- propertyType.Variations = ContentVariation.Nothing;
- break;
- case ContentVariation.Culture: // changing to Culture
- // all property types can remain Nothing
- break;
- case ContentVariation.CultureAndSegment:
- case ContentVariation.Segment:
- default:
- throw new NotSupportedException(); // TODO: Support this
- }
- }
-
- // then, track each property individually
+ // track each property individually
if (propertyType.IsPropertyDirty("Variations"))
{
// allocate the list only when needed
@@ -455,23 +438,19 @@ AND umbracoNode.id <> @id",
// via composition, with their original variations (ie not filtered by this
// content type variations - we need this true value to make decisions.
- foreach (var propertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
+ propertyTypeVariationChanges = propertyTypeVariationChanges ?? new Dictionary();
+
+ foreach (var composedPropertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
{
- if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment())
- throw new NotSupportedException(); // TODO: support this
+ if (composedPropertyType.Variations == ContentVariation.Nothing) continue;
- if (propertyType.Variations == ContentVariation.Culture)
- {
- if (propertyTypeVariationChanges == null)
- propertyTypeVariationChanges = new Dictionary();
+ // Determine target variation of the composed property type.
+ // The composed property is only considered culture variant when the base content type is also culture variant.
+ // The composed property is only considered segment variant when the base content type is also segment variant.
+ // Example: Culture variant content type with a Culture+Segment variant property type will become ContentVariation.Culture
+ var target = newContentTypeVariation & composedPropertyType.Variations;
- // if content type moves to Culture, property type becomes Culture here again
- // if content type moves to Nothing, property type becomes Nothing here
- if (newContentTypeVariation == ContentVariation.Culture)
- propertyTypeVariationChanges[propertyType.Id] = (ContentVariation.Nothing, ContentVariation.Culture);
- else if (newContentTypeVariation == ContentVariation.Nothing)
- propertyTypeVariationChanges[propertyType.Id] = (ContentVariation.Culture, ContentVariation.Nothing);
- }
+ propertyTypeVariationChanges[composedPropertyType.Id] = (composedPropertyType.Variations, target);
}
}
@@ -512,7 +491,7 @@ AND umbracoNode.id <> @id",
var impacted = GetImpactedContentTypes(entity, all);
// if some property types have actually changed, move their variant data
- if (propertyTypeVariationChanges != null)
+ if (propertyTypeVariationChanges?.Count > 0)
MovePropertyTypeVariantData(propertyTypeVariationChanges, impacted);
// deal with orphan properties: those that were in a deleted tab,
@@ -524,23 +503,40 @@ AND umbracoNode.id <> @id",
CommonRepository.ClearCache(); // always
}
+ ///
+ /// Corrects the property type variations for the given entity
+ /// to make sure the property type variation is compatible with the
+ /// variation set on the entity itself.
+ ///
+ /// Entity to correct properties for
+ private void CorrectPropertyTypeVariations(IContentTypeComposition entity)
+ {
+ // Update property variations based on the content type variation
+ foreach (var propertyType in entity.PropertyTypes)
+ {
+ // Determine variation for the property type.
+ // The property is only considered culture variant when the base content type is also culture variant.
+ // The property is only considered segment variant when the base content type is also segment variant.
+ // Example: Culture variant content type with a Culture+Segment variant property type will become ContentVariation.Culture
+ propertyType.Variations = entity.Variations & propertyType.Variations;
+ }
+ }
+
///
/// Ensures that no property types are flagged for a variance that is not supported by the content type itself
///
- ///
+ /// The entity for which the property types will be validated
private void ValidateVariations(IContentTypeComposition entity)
{
- //if the entity does not vary at all, then the property cannot have a variance value greater than it
- if (entity.Variations == ContentVariation.Nothing)
+ foreach (var prop in entity.PropertyTypes)
{
- foreach (var prop in entity.PropertyTypes)
- {
- if (prop.IsPropertyDirty(nameof(prop.Variations)) && prop.Variations > entity.Variations)
- throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
- }
-
+ // The variation of a property is only allowed if all its variation flags
+ // are also set on the entity itself. It cannot set anything that is not also set by the content type.
+ // For example, when entity.Variations is set to Culture a property cannot be set to Segment.
+ var isValid = entity.Variations.HasFlag(prop.Variations);
+ if (!isValid)
+ throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
}
-
}
private IEnumerable GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable all)
@@ -661,27 +657,27 @@ AND umbracoNode.id <> @id",
var impactedL = impacted.Select(x => x.Id).ToList();
//Group by the "To" variation so we can bulk update in the correct batches
- foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
+ foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value))
{
var propertyTypeIds = grouping.Select(x => x.Key).ToList();
- var toVariation = grouping.Key;
+ var (FromVariation, ToVariation) = grouping.Key;
- switch (toVariation)
+ var fromCultureEnabled = FromVariation.HasFlag(ContentVariation.Culture);
+ var toCultureEnabled = ToVariation.HasFlag(ContentVariation.Culture);
+
+ if (!fromCultureEnabled && toCultureEnabled)
{
- case ContentVariation.Culture:
- CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
- CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
- RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
- break;
- case ContentVariation.Nothing:
- CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
- CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
- RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
- break;
- case ContentVariation.CultureAndSegment:
- case ContentVariation.Segment:
- default:
- throw new NotSupportedException(); // TODO: Support this
+ // Culture has been enabled
+ CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
+ CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
+ RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
+ }
+ else if (fromCultureEnabled && !toCultureEnabled)
+ {
+ // Culture has been disabled
+ CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
+ CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
+ RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
}
}
}
@@ -693,78 +689,72 @@ AND umbracoNode.id <> @id",
{
var defaultLanguageId = GetDefaultLanguageId();
- switch (toVariation)
+ var cultureIsNotEnabled = !fromVariation.HasFlag(ContentVariation.Culture);
+ var cultureWillBeEnabled = toVariation.HasFlag(ContentVariation.Culture);
+
+ if (cultureIsNotEnabled && cultureWillBeEnabled)
{
- case ContentVariation.Culture:
+ //move the names
+ //first clear out any existing names that might already exists under the default lang
+ //there's 2x tables to update
- //move the names
- //first clear out any existing names that might already exists under the default lang
- //there's 2x tables to update
+ //clear out the versionCultureVariation table
+ var sqlSelect = Sql().Select(x => x.Id)
+ .From()
+ .InnerJoin().On(x => x.Id, x => x.VersionId)
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .Where(x => x.ContentTypeId == contentType.Id)
+ .Where(x => x.LanguageId == defaultLanguageId);
+ var sqlDelete = Sql()
+ .Delete()
+ .WhereIn(x => x.Id, sqlSelect);
- //clear out the versionCultureVariation table
- var sqlSelect = Sql().Select(x => x.Id)
- .From()
- .InnerJoin().On(x => x.Id, x => x.VersionId)
- .InnerJoin().On(x => x.NodeId, x => x.NodeId)
- .Where(x => x.ContentTypeId == contentType.Id)
- .Where(x => x.LanguageId == defaultLanguageId);
- var sqlDelete = Sql()
- .Delete()
- .WhereIn(x => x.Id, sqlSelect);
+ Database.Execute(sqlDelete);
- Database.Execute(sqlDelete);
+ //clear out the documentCultureVariation table
+ sqlSelect = Sql().Select(x => x.Id)
+ .From()
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .Where(x => x.ContentTypeId == contentType.Id)
+ .Where(x => x.LanguageId == defaultLanguageId);
+ sqlDelete = Sql()
+ .Delete()
+ .WhereIn(x => x.Id, sqlSelect);
- //clear out the documentCultureVariation table
- sqlSelect = Sql().Select(x => x.Id)
- .From()
- .InnerJoin().On(x => x.NodeId, x => x.NodeId)
- .Where(x => x.ContentTypeId == contentType.Id)
- .Where(x => x.LanguageId == defaultLanguageId);
- sqlDelete = Sql()
- .Delete()
- .WhereIn(x => x.Id, sqlSelect);
+ Database.Execute(sqlDelete);
- Database.Execute(sqlDelete);
+ //now we need to insert names into these 2 tables based on the invariant data
- //now we need to insert names into these 2 tables based on the invariant data
+ //insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
+ var cols = Sql().Columns(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
+ sqlSelect = Sql().Select(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
+ .Append($", {defaultLanguageId}") //default language ID
+ .From()
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .Where(x => x.ContentTypeId == contentType.Id);
+ var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
- //insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
- var cols = Sql().Columns(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
- sqlSelect = Sql().Select(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
- .Append($", {defaultLanguageId}") //default language ID
- .From()
- .InnerJoin().On(x => x.NodeId, x => x.NodeId)
- .Where(x => x.ContentTypeId == contentType.Id);
- var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
+ Database.Execute(sqlInsert);
- Database.Execute(sqlInsert);
+ //insert rows into the documentCultureVariation table
+ cols = Sql().Columns(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
+ sqlSelect = Sql().Select(x => x.NodeId, x => x.Edited, x => x.Published)
+ .AndSelect(x => x.Text)
+ .Append($", 1, {defaultLanguageId}") //make Available + default language ID
+ .From()
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .Where(x => x.ContentTypeId == contentType.Id);
+ sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
- //insert rows into the documentCultureVariation table
- cols = Sql().Columns(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
- sqlSelect = Sql().Select(x => x.NodeId, x => x.Edited, x => x.Published)
- .AndSelect(x => x.Text)
- .Append($", 1, {defaultLanguageId}") //make Available + default language ID
- .From()
- .InnerJoin().On(x => x.NodeId, x => x.NodeId)
- .InnerJoin().On(x => x.NodeId, x => x.NodeId)
- .Where(x => x.ContentTypeId == contentType.Id);
- sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
+ Database.Execute(sqlInsert);
+ }
+ else
+ {
+ //we don't need to move the names! this is because we always keep the invariant names with the name of the default language.
- Database.Execute(sqlInsert);
-
- break;
- case ContentVariation.Nothing:
-
- //we don't need to move the names! this is because we always keep the invariant names with the name of the default language.
-
- //however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
- // if we want these SQL statements back, look into GIT history
-
- break;
- case ContentVariation.CultureAndSegment:
- case ContentVariation.Segment:
- default:
- throw new NotSupportedException(); // TODO: Support this
+ //however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
+ // if we want these SQL statements back, look into GIT history
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs
index dac8fda5ec..9ccf6e9623 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs
@@ -279,6 +279,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return moveInfo;
}
+ public IReadOnlyDictionary> FindUsages(int id)
+ {
+ if (id == default)
+ return new Dictionary>();
+
+ var sql = Sql()
+ .Select(ct => ct.Select(node => node.NodeDto))
+ .AndSelect(pt => Alias(pt.Alias, "ptAlias"), pt => Alias(pt.Name, "ptName"))
+ .From()
+ .InnerJoin().On(ct => ct.NodeId, pt => pt.ContentTypeId)
+ .InnerJoin().On(n => n.NodeId, ct => ct.NodeId)
+ .Where(pt => pt.DataTypeId == id)
+ .OrderBy(node => node.NodeId)
+ .AndBy(pt => pt.Alias);
+
+ var dtos = Database.FetchOneToMany(ct => ct.PropertyTypes, sql);
+
+ return dtos.ToDictionary(
+ x => (Udi)new GuidUdi(ObjectTypes.GetUdiType(x.NodeDto.NodeObjectType.Value), x.NodeDto.UniqueId).EnsureClosed(),
+ x => (IEnumerable)x.PropertyTypes.Select(p => p.Alias).ToList());
+ }
+
private string EnsureUniqueNodeName(string nodeName, int id = 0)
{
var template = SqlContext.Templates.Get("Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName", tsql => tsql
@@ -291,5 +313,24 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return SimilarNodeName.GetUniqueName(names, id, nodeName);
}
+
+
+ [TableName(Constants.DatabaseSchema.Tables.ContentType)]
+ private class ContentTypeReferenceDto : ContentTypeDto
+ {
+ [ResultColumn]
+ [Reference(ReferenceType.Many)]
+ public List PropertyTypes { get; set; }
+ }
+
+ [TableName(Constants.DatabaseSchema.Tables.PropertyType)]
+ private class PropertyTypeReferenceDto
+ {
+ [Column("ptAlias")]
+ public string Alias { get; set; }
+
+ [Column("ptName")]
+ public string Name { get; set; }
+ }
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs
index 1a8b2b8821..a905294417 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs
@@ -182,6 +182,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
throw new InvalidOperationException($"Cannot save the default language ({entity.IsoCode}) as non-default. Make another language the default language instead.");
}
+ if (entity.IsPropertyDirty(nameof(ILanguage.IsoCode)))
+ {
+ //if the iso code is changing, ensure there's not another lang with the same code already assigned
+ var sameCode = Sql()
+ .SelectCount()
+ .From()
+ .Where(x => x.IsoCode == entity.IsoCode && x.Id != entity.Id);
+
+ var countOfSameCode = Database.ExecuteScalar(sameCode);
+ if (countOfSameCode > 0)
+ throw new InvalidOperationException($"Cannot update the language to a new culture: {entity.IsoCode} since that culture is already assigned to another language entity.");
+ }
+
// fallback cycles are detected at service level
// update
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
index 1fc3568fc0..892122dff9 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
@@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// joining the type so we can do a query against the member type - not sure if this adds much overhead or not?
// the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content
- // types by default on the document and media repo's so we can query by content type there too.
+ // types by default on the document and media repos so we can query by content type there too.
.InnerJoin().On(left => left.ContentTypeId, right => right.NodeId);
sql.Where(x => x.NodeObjectType == NodeObjectTypeId);
@@ -546,6 +546,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ordering.OrderBy.InvariantEquals("userName"))
return SqlSyntax.GetFieldName(x => x.LoginName);
+ if (ordering.OrderBy.InvariantEquals("updateDate"))
+ return SqlSyntax.GetFieldName(x => x.VersionDate);
+
+ if (ordering.OrderBy.InvariantEquals("createDate"))
+ return SqlSyntax.GetFieldName(x => x.CreateDate);
+
+ if (ordering.OrderBy.InvariantEquals("contentTypeAlias"))
+ return SqlSyntax.GetFieldName(x => x.Alias);
+
return base.ApplySystemOrdering(ref sql, ordering);
}
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
index 55625ff04e..7ae001bf24 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Data;
using System.Text.RegularExpressions;
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
@@ -76,6 +77,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax
string ConvertIntegerToOrderableString { get; }
string ConvertDateToOrderableString { get; }
string ConvertDecimalToOrderableString { get; }
+
+ ///
+ /// Returns the default isolation level for the database
+ ///
+ IsolationLevel DefaultIsolationLevel { get; }
IEnumerable GetTablesInSchema(IDatabase db);
IEnumerable GetColumnsInSchema(IDatabase db);
@@ -121,5 +127,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// unspecified.
///
bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
+
+ void ReadLock(IDatabase db, params int[] lockIds);
+ void WriteLock(IDatabase db, params int[] lockIds);
}
}
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs
index cb4b7a5176..2ed0fb878c 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.Data;
+using System.Data.SqlServerCe;
using System.Linq;
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
@@ -52,6 +54,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return "(" + string.Join("+", args) + ")";
}
+ public override System.Data.IsolationLevel DefaultIsolationLevel => System.Data.IsolationLevel.RepeatableRead;
+
public override string FormatColumnRename(string tableName, string oldName, string newName)
{
//NOTE Sql CE doesn't support renaming a column, so a new column needs to be created, then copy data and finally remove old column
@@ -152,6 +156,39 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
return result > 0;
}
+ public override void WriteLock(IDatabase db, params int[] lockIds)
+ {
+ // soon as we get Database, a transaction is started
+
+ if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
+ throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
+
+ db.Execute(@"SET LOCK_TIMEOUT 1800;");
+ // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
+ foreach (var lockId in lockIds)
+ {
+ var i = db.Execute(@"UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
+ if (i == 0) // ensure we are actually locking!
+ throw new ArgumentException($"LockObject with id={lockId} does not exist.");
+ }
+ }
+
+ public override void ReadLock(IDatabase db, params int[] lockIds)
+ {
+ // soon as we get Database, a transaction is started
+
+ if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
+ throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
+
+ // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
+ foreach (var lockId in lockIds)
+ {
+ var i = db.ExecuteScalar("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId });
+ if (i == null) // ensure we are actually locking!
+ throw new ArgumentException($"LockObject with id={lockId} does not exist.");
+ }
+ }
+
protected override string FormatIdentity(ColumnDefinition column)
{
return column.IsIdentity ? GetIdentityString(column) : string.Empty;
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
index fab7526a6b..3d0adf175e 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
+using System.Data.SqlClient;
using System.Linq;
using NPoco;
using Umbraco.Core.Logging;
@@ -179,6 +180,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return items.Select(x => x.TABLE_NAME).Cast().ToList();
}
+ public override IsolationLevel DefaultIsolationLevel => IsolationLevel.ReadCommitted;
+
public override IEnumerable GetColumnsInSchema(IDatabase db)
{
var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
@@ -246,6 +249,41 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
return result > 0;
}
+ public override void WriteLock(IDatabase db, params int[] lockIds)
+ {
+ // soon as we get Database, a transaction is started
+
+ if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
+ throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
+
+
+ // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
+ foreach (var lockId in lockIds)
+ {
+ db.Execute(@"SET LOCK_TIMEOUT 1800;");
+ var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
+ if (i == 0) // ensure we are actually locking!
+ throw new ArgumentException($"LockObject with id={lockId} does not exist.");
+ }
+ }
+
+
+ public override void ReadLock(IDatabase db, params int[] lockIds)
+ {
+ // soon as we get Database, a transaction is started
+
+ if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
+ throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
+
+ // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
+ foreach (var lockId in lockIds)
+ {
+ var i = db.ExecuteScalar("SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id", new { id = lockId });
+ if (i == null) // ensure we are actually locking!
+ throw new ArgumentException($"LockObject with id={lockId} does not exist.", nameof(lockIds));
+ }
+ }
+
public override string FormatColumnRename(string tableName, string oldName, string newName)
{
return string.Format(RenameColumn, tableName, oldName, newName);
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
index 0c27ac2d50..b2e03df96e 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
@@ -200,7 +200,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return "NVARCHAR";
}
-
+
+ public abstract IsolationLevel DefaultIsolationLevel { get; }
+
public virtual IEnumerable GetTablesInSchema(IDatabase db)
{
return new List();
@@ -225,6 +227,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
public abstract bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
+ public abstract void ReadLock(IDatabase db, params int[] lockIds);
+ public abstract void WriteLock(IDatabase db, params int[] lockIds);
+
public virtual bool DoesTableExist(IDatabase db, string tableName)
{
return false;
diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
index 51e0172f35..072813b4e6 100644
--- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
+++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
@@ -20,9 +20,6 @@ namespace Umbraco.Core.Persistence
///
public class UmbracoDatabase : Database, IUmbracoDatabase
{
- // Umbraco's default isolation level is RepeatableRead
- private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
-
private readonly ILogger _logger;
private readonly RetryPolicy _connectionRetryPolicy;
private readonly RetryPolicy _commandRetryPolicy;
@@ -38,7 +35,7 @@ namespace Umbraco.Core.Persistence
/// Also used by DatabaseBuilder for creating databases and installing/upgrading.
///
public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
- : base(connectionString, sqlContext.DatabaseType, provider, DefaultIsolationLevel)
+ : base(connectionString, sqlContext.DatabaseType, provider, sqlContext.SqlSyntax.DefaultIsolationLevel)
{
SqlContext = sqlContext;
@@ -54,7 +51,7 @@ namespace Umbraco.Core.Persistence
///
/// Internal for unit tests only.
internal UmbracoDatabase(DbConnection connection, ISqlContext sqlContext, ILogger logger)
- : base(connection, sqlContext.DatabaseType, DefaultIsolationLevel)
+ : base(connection, sqlContext.DatabaseType, sqlContext.SqlSyntax.DefaultIsolationLevel)
{
SqlContext = sqlContext;
_logger = logger;
diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs
index e9dd04c5fa..84273e23da 100644
--- a/src/Umbraco.Core/Scoping/Scope.cs
+++ b/src/Umbraco.Core/Scoping/Scope.cs
@@ -33,8 +33,6 @@ namespace Umbraco.Core.Scoping
private ICompletable _fscope;
private IEventDispatcher _eventDispatcher;
- private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
-
// initializes a new scope
private Scope(ScopeProvider scopeProvider,
ILogger logger, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable,
@@ -205,7 +203,7 @@ namespace Umbraco.Core.Scoping
{
if (_isolationLevel != IsolationLevel.Unspecified) return _isolationLevel;
if (ParentScope != null) return ParentScope.IsolationLevel;
- return DefaultIsolationLevel;
+ return Database.SqlContext.SqlSyntax.DefaultIsolationLevel;
}
}
@@ -488,37 +486,9 @@ namespace Umbraco.Core.Scoping
?? (_logUncompletedScopes = Current.Configs.CoreDebug().LogUncompletedScopes)).Value;
///
- public void ReadLock(params int[] lockIds)
- {
- // soon as we get Database, a transaction is started
-
- if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
- throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
-
- // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
- foreach (var lockId in lockIds)
- {
- var i = Database.ExecuteScalar("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId });
- if (i == null) // ensure we are actually locking!
- throw new Exception($"LockObject with id={lockId} does not exist.");
- }
- }
+ public void ReadLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.ReadLock(Database, lockIds);
///
- public void WriteLock(params int[] lockIds)
- {
- // soon as we get Database, a transaction is started
-
- if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
- throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
-
- // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
- foreach (var lockId in lockIds)
- {
- var i = Database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
- if (i == 0) // ensure we are actually locking!
- throw new Exception($"LockObject with id={lockId} does not exist.");
- }
- }
+ public void WriteLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.WriteLock(Database, lockIds);
}
}
diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
index b86494adb5..51e5d756eb 100644
--- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
+++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
@@ -25,7 +25,7 @@ namespace Umbraco.Core.Services
///
/// Gets a content type.
///
- TItem Get(int id);
+ new TItem Get(int id);
///
/// Gets a content type.
@@ -40,6 +40,7 @@ namespace Umbraco.Core.Services
int Count();
IEnumerable GetAll(params int[] ids);
+ IEnumerable GetAll(IEnumerable ids);
IEnumerable GetDescendants(int id, bool andSelf); // parent-child axis
IEnumerable GetComposedOf(int id); // composition axis
diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs
index bb56e110cd..3ebfa95bfb 100644
--- a/src/Umbraco.Core/Services/IDataTypeService.cs
+++ b/src/Umbraco.Core/Services/IDataTypeService.cs
@@ -10,6 +10,13 @@ namespace Umbraco.Core.Services
///
public interface IDataTypeService : IService
{
+ ///
+ /// Returns a dictionary of content type s and the property type aliases that use a
+ ///
+ ///
+ ///
+ IReadOnlyDictionary> GetReferences(int id);
+
Attempt> CreateContainer(int parentId, string name, int userId = Constants.Security.SuperUserId);
Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId);
EntityContainer GetContainer(int containerId);
diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index 6ac8e1404a..705a876d83 100644
--- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -252,12 +252,12 @@ namespace Umbraco.Core.Services.Implement
}
}
- public IEnumerable GetAll(params Guid[] ids)
+ public IEnumerable GetAll(IEnumerable ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(ReadLockIds);
- return Repository.GetMany(ids);
+ return Repository.GetMany(ids.ToArray());
}
}
diff --git a/src/Umbraco.Core/Services/Implement/DataTypeService.cs b/src/Umbraco.Core/Services/Implement/DataTypeService.cs
index dc998b18dd..5a93fb91b1 100644
--- a/src/Umbraco.Core/Services/Implement/DataTypeService.cs
+++ b/src/Umbraco.Core/Services/Implement/DataTypeService.cs
@@ -466,6 +466,14 @@ namespace Umbraco.Core.Services.Implement
}
}
+ public IReadOnlyDictionary> GetReferences(int id)
+ {
+ using (var scope = ScopeProvider.CreateScope(autoComplete:true))
+ {
+ return _dataTypeRepository.FindUsages(id);
+ }
+ }
+
private void Audit(AuditType type, int userId, int objectId)
{
_auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.DataType)));
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 43d168f442..80db137b6c 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -984,7 +984,6 @@
-
diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs
index f4478e2add..33855a8bfb 100644
--- a/src/Umbraco.Tests/Composing/CompositionTests.cs
+++ b/src/Umbraco.Tests/Composing/CompositionTests.cs
@@ -4,6 +4,7 @@ using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
+using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Tests.Composing
@@ -35,7 +36,7 @@ namespace Umbraco.Tests.Composing
.Returns(() => factoryFactory?.Invoke(mockedFactory));
var logger = new ProfilingLogger(Mock.Of(), Mock.Of());
- var typeLoader = new TypeLoader(Mock.Of(), "", logger);
+ var typeLoader = new TypeLoader(Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), logger);
var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of());
// create the factory, ensure it is the mocked factory
diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs
similarity index 97%
rename from src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs
rename to src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs
index 394a33d777..ec6b854a46 100644
--- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs
+++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs
@@ -21,7 +21,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
///
/// Implements a published snapshot service.
///
- internal class PublishedSnapshotService : PublishedSnapshotServiceBase
+ internal class XmlPublishedSnapshotService : PublishedSnapshotServiceBase
{
private readonly XmlStore _xmlStore;
private readonly RoutesCache _routesCache;
@@ -41,7 +41,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
#region Constructors
// used in WebBootManager + tests
- public PublishedSnapshotService(ServiceContext serviceContext,
+ public XmlPublishedSnapshotService(ServiceContext serviceContext,
IPublishedContentTypeFactory publishedContentTypeFactory,
IScopeProvider scopeProvider,
IAppCache requestCache,
@@ -65,7 +65,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
}
// used in some tests
- internal PublishedSnapshotService(ServiceContext serviceContext,
+ internal XmlPublishedSnapshotService(ServiceContext serviceContext,
IPublishedContentTypeFactory publishedContentTypeFactory,
IScopeProvider scopeProvider,
IAppCache requestCache,
diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs
index 3b675c2f07..447104b7cd 100644
--- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs
+++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs
@@ -32,7 +32,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
/// Represents the Xml storage for the Xml published cache.
///
///
- /// One instance of is instantiated by the and
+ /// One instance of is instantiated by the and
/// then passed to all instances that are created (one per request).
/// This class should *not* be public.
///
diff --git a/src/Umbraco.Tests/Models/UserExtensionsTests.cs b/src/Umbraco.Tests/Models/UserExtensionsTests.cs
index d9c8057377..bf76031b59 100644
--- a/src/Umbraco.Tests/Models/UserExtensionsTests.cs
+++ b/src/Umbraco.Tests/Models/UserExtensionsTests.cs
@@ -57,6 +57,9 @@ namespace Umbraco.Tests.Models
[TestCase("1,-1", "1", "1")] // was an issue
[TestCase("-1,1", "1", "1")] // was an issue
+ [TestCase("-1", "", "-1")]
+ [TestCase("", "-1", "-1")]
+
public void CombineStartNodes(string groupSn, string userSn, string expected)
{
// 1
diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests/Persistence/LocksTests.cs
index 56779ace0f..afcd481f9f 100644
--- a/src/Umbraco.Tests/Persistence/LocksTests.cs
+++ b/src/Umbraco.Tests/Persistence/LocksTests.cs
@@ -5,7 +5,6 @@ using System.Threading;
using NPoco;
using NUnit.Framework;
using Umbraco.Core;
-using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
@@ -37,7 +36,7 @@ namespace Umbraco.Tests.Persistence
{
using (var scope = ScopeProvider.CreateScope())
{
- scope.Database.AcquireLockNodeReadLock(Constants.Locks.Servers);
+ scope.ReadLock(Constants.Locks.Servers);
scope.Complete();
}
}
@@ -62,7 +61,7 @@ namespace Umbraco.Tests.Persistence
{
try
{
- scope.Database.AcquireLockNodeReadLock(Constants.Locks.Servers);
+ scope.ReadLock(Constants.Locks.Servers);
lock (locker)
{
acquired++;
@@ -131,7 +130,7 @@ namespace Umbraco.Tests.Persistence
if (entered == threadCount) m1.Set();
}
ms[ic].WaitOne();
- scope.Database.AcquireLockNodeWriteLock(Constants.Locks.Servers);
+ scope.WriteLock(Constants.Locks.Servers);
lock (locker)
{
acquired++;
@@ -221,7 +220,7 @@ namespace Umbraco.Tests.Persistence
{
otherEv.WaitOne();
Console.WriteLine($"[{id1}] WAIT {id1}");
- scope.Database.AcquireLockNodeWriteLock(id1);
+ scope.WriteLock(id1);
Console.WriteLine($"[{id1}] GRANT {id1}");
WriteLocks(scope.Database);
myEv.Set();
@@ -232,7 +231,7 @@ namespace Umbraco.Tests.Persistence
Thread.Sleep(200); // cannot wait due to deadlock... just give it a bit of time
Console.WriteLine($"[{id1}] WAIT {id2}");
- scope.Database.AcquireLockNodeWriteLock(id2);
+ scope.WriteLock(id2);
Console.WriteLine($"[{id1}] GRANT {id2}");
WriteLocks(scope.Database);
}
@@ -284,7 +283,7 @@ namespace Umbraco.Tests.Persistence
{
otherEv.WaitOne();
Console.WriteLine($"[{id}] WAIT {id}");
- scope.Database.AcquireLockNodeWriteLock(id);
+ scope.WriteLock(id);
Console.WriteLine($"[{id}] GRANT {id}");
WriteLocks(scope.Database);
myEv.Set();
diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs
index b558ce6c87..ca8ee29ee3 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs
@@ -28,6 +28,68 @@ namespace Umbraco.Tests.Persistence.Repositories
return new EntityContainerRepository(scopeAccessor, AppCaches.Disabled, Logger, Constants.ObjectTypes.DataTypeContainer);
}
+ [Test]
+ public void Can_Find_Usages()
+ {
+ var provider = TestObjects.GetScopeProvider(Logger);
+
+ using (provider.CreateScope())
+ {
+ var dtRepo = CreateRepository();
+ IDataType dataType1 = new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService)) { Name = "dt1" };
+ dtRepo.Save(dataType1);
+ IDataType dataType2 = new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService)) { Name = "dt2" };
+ dtRepo.Save(dataType2);
+
+ var ctRepo = Factory.GetInstance();
+ IContentType ct = new ContentType(-1)
+ {
+ Alias = "ct1",
+ Name = "CT1",
+ AllowedAsRoot = true,
+ Icon = "icon-home",
+ PropertyGroups = new PropertyGroupCollection
+ {
+ new PropertyGroup(true)
+ {
+ Name = "PG1",
+ PropertyTypes = new PropertyTypeCollection(true)
+ {
+ new PropertyType(dataType1, "pt1")
+ {
+ Name = "PT1"
+ },
+ new PropertyType(dataType1, "pt2")
+ {
+ Name = "PT2"
+ },
+ new PropertyType(dataType2, "pt3")
+ {
+ Name = "PT3"
+ }
+ }
+ }
+ }
+ };
+ ctRepo.Save(ct);
+
+ var usages = dtRepo.FindUsages(dataType1.Id);
+
+ var key = usages.First().Key;
+ Assert.AreEqual(ct.Key, ((GuidUdi)key).Guid);
+ Assert.AreEqual(2, usages[key].Count());
+ Assert.AreEqual("pt1", usages[key].ElementAt(0));
+ Assert.AreEqual("pt2", usages[key].ElementAt(1));
+
+ usages = dtRepo.FindUsages(dataType2.Id);
+
+ key = usages.First().Key;
+ Assert.AreEqual(ct.Key, ((GuidUdi)key).Guid);
+ Assert.AreEqual(1, usages[key].Count());
+ Assert.AreEqual("pt3", usages[key].ElementAt(0));
+ }
+ }
+
[Test]
public void Can_Move()
{
diff --git a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs
index 03c1713268..85a3374cea 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs
@@ -1,4 +1,5 @@
-using System.Globalization;
+using System;
+using System.Globalization;
using System.Linq;
using Moq;
using NUnit.Framework;
@@ -297,6 +298,24 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
+ [Test]
+ public void Perform_Update_With_Existing_Culture()
+ {
+ // Arrange
+ var provider = TestObjects.GetScopeProvider(Logger);
+ using (var scope = provider.CreateScope())
+ {
+ var repository = CreateRepository(provider);
+
+ // Act
+ var language = repository.Get(5);
+ language.IsoCode = "da-DK";
+ language.CultureName = "da-DK";
+
+ Assert.Throws(() => repository.Save(language));
+ }
+ }
+
[Test]
public void Can_Perform_Delete_On_LanguageRepository()
{
diff --git a/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs b/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs
index 5651dd1457..02ad6b3971 100644
--- a/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs
+++ b/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs
@@ -14,7 +14,7 @@ namespace Umbraco.Tests.Persistence
public void ReadLockNonExisting()
{
var provider = TestObjects.GetScopeProvider(Logger);
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
using (var scope = provider.CreateScope())
{
@@ -39,7 +39,7 @@ namespace Umbraco.Tests.Persistence
public void WriteLockNonExisting()
{
var provider = TestObjects.GetScopeProvider(Logger);
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
using (var scope = provider.CreateScope())
{
diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
index e615933c44..5d32606ee7 100644
--- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
@@ -41,6 +41,12 @@ namespace Umbraco.Tests.PublishedContent
private ContentType _contentTypeVariant;
private TestDataSource _source;
+ [TearDown]
+ public void Teardown()
+ {
+ _snapshotService?.Dispose();
+ }
+
private void Init(IEnumerable kits)
{
Current.Reset();
diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs
index 44df5c20cb..0e05e6baad 100644
--- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs
@@ -36,6 +36,12 @@ namespace Umbraco.Tests.PublishedContent
private ContentType _contentType;
private PropertyType _propertyType;
+ [TearDown]
+ public void Teardown()
+ {
+ _snapshotService?.Dispose();
+ }
+
private void Init()
{
Current.Reset();
@@ -303,5 +309,6 @@ namespace Umbraco.Tests.PublishedContent
Assert.IsFalse(c2.IsPublished("dk-DA"));
Assert.IsTrue(c2.IsPublished("de-DE"));
}
+
}
}
diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs
index 34482a79fa..91a6934e18 100644
--- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs
+++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs
@@ -73,7 +73,7 @@ namespace Umbraco.Tests.Scoping
// xmlStore.Xml - the actual main xml document
// publishedContentCache.GetXml() - the captured xml
- private static XmlStore XmlStore => (Current.Factory.GetInstance() as PublishedSnapshotService).XmlStore;
+ private static XmlStore XmlStore => (Current.Factory.GetInstance() as XmlPublishedSnapshotService).XmlStore;
private static XmlDocument XmlMaster => XmlStore.Xml;
private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.Content).GetXml(false);
diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs
index d618bcf1ad..0fe05f385f 100644
--- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs
+++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs
@@ -105,13 +105,26 @@ namespace Umbraco.Tests.Services
}
}
- [Test]
- public void Change_Content_Type_Variation_Clears_Redirects()
+ [TestCase(ContentVariation.Nothing, ContentVariation.Nothing, false)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.Culture, true)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment, true)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.Segment, true)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Nothing, true)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Culture, false)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Segment, true)]
+ [TestCase(ContentVariation.Culture, ContentVariation.CultureAndSegment, true)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Nothing, true)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Culture, true)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Segment, false)]
+ [TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment, true)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing, true)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Culture, true)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment, true)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.CultureAndSegment, false)]
+ public void Change_Content_Type_Variation_Clears_Redirects(ContentVariation startingContentTypeVariation, ContentVariation changedContentTypeVariation, bool shouldUrlRedirectsBeCleared)
{
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Nothing;
- var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
- contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = startingContentTypeVariation;
ServiceContext.ContentTypeService.Save(contentType);
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
ServiceContext.ContentTypeService.Save(contentType2);
@@ -119,6 +132,11 @@ namespace Umbraco.Tests.Services
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Hello1";
+ if(startingContentTypeVariation.HasFlag(ContentVariation.Culture))
+ {
+ doc.SetCultureName(doc.Name, "en-US");
+ }
+
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
@@ -127,24 +145,27 @@ namespace Umbraco.Tests.Services
ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
+ // These 2 assertions should probably be moved to a test for the Register() method?
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
//change variation
- contentType.Variations = ContentVariation.Culture;
+ contentType.Variations = changedContentTypeVariation;
ServiceContext.ContentTypeService.Save(contentType);
-
- Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
+ var expectedRedirectUrlCount = shouldUrlRedirectsBeCleared ? 0 : 1;
+ Assert.AreEqual(expectedRedirectUrlCount, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
-
}
- [Test]
- public void Change_Content_Type_From_Invariant_Variant()
- {
+ [TestCase(ContentVariation.Nothing, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)]
+ public void Change_Content_Type_From_No_Culture_To_Culture(ContentVariation from, ContentVariation to)
+ {
var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Nothing;
- var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ contentType.Variations = from;
+ var properties = CreatePropertyCollection(("title", from));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -159,12 +180,12 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("Hello1", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.Edited);
- Assert.IsFalse (doc.IsCultureEdited("en-US"));
+ Assert.IsFalse(doc.IsCultureEdited("en-US"));
//change the content type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello2";
ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Culture;
+ contentType.Variations = to;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -176,7 +197,7 @@ namespace Umbraco.Tests.Services
//change back property type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello3", "en-US");
ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Nothing;
+ contentType.Variations = from;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -186,12 +207,15 @@ namespace Umbraco.Tests.Services
Assert.IsFalse(doc.IsCultureEdited("en-US"));
}
- [Test]
- public void Change_Content_Type_From_Variant_Invariant()
+ [TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Segment)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
+ public void Change_Content_Type_From_Culture_To_No_Culture(ContentVariation startingContentTypeVariation, ContentVariation changeContentTypeVariationTo)
{
var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ contentType.Variations = startingContentTypeVariation;
+ var properties = CreatePropertyCollection(("title", startingContentTypeVariation));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -210,7 +234,7 @@ namespace Umbraco.Tests.Services
//change the content type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello2", "en-US");
ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Nothing;
+ contentType.Variations = changeContentTypeVariationTo;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -222,19 +246,20 @@ namespace Umbraco.Tests.Services
//change back property type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello3";
ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Culture;
+ contentType.Variations = startingContentTypeVariation;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
//at this stage all property types were switched to invariant so even though the variant value
//exists it will not be returned because the property type is invariant,
//so this check proves that null will be returned
+ Assert.AreEqual("Hello3", doc.Name);
Assert.IsNull(doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US")); // this is true because the name change is copied to the default language
//we can now switch the property type to be variant and the value can be returned again
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ contentType.PropertyTypes.First().Variations = startingContentTypeVariation;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -242,32 +267,129 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US"));
-
}
-
- [Test]
- public void Change_Property_Type_From_To_Variant_On_Invariant_Content_Type()
+ [TestCase(ContentVariation.Nothing, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.Segment)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Segment)]
+ [TestCase(ContentVariation.Culture, ContentVariation.CultureAndSegment)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Segment)]
+ [TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Culture)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.CultureAndSegment)]
+ public void Preserve_Content_Name_After_Content_Type_Variation_Change(ContentVariation contentTypeVariationFrom, ContentVariation contentTypeVariationTo)
{
var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Nothing;
- var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ contentType.Variations = contentTypeVariationFrom;
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ var invariantContentName = "Content Invariant";
+
+ var defaultCultureContentName = "Content en-US";
+ var defaultCulture = "en-US";
+
+ var nlContentName = "Content nl-NL";
+ var nlCulture = "nl-NL";
+
+ ServiceContext.LocalizationService.Save(new Language(nlCulture));
+
+ var includeCultureNames = contentType.Variations.HasFlag(ContentVariation.Culture);
+
+ // Create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+
+ doc.Name = invariantContentName;
+ if (includeCultureNames)
+ {
+ Assert.DoesNotThrow(() => doc.SetCultureName(defaultCultureContentName, defaultCulture));
+ Assert.DoesNotThrow(() => doc.SetCultureName(nlContentName, nlCulture));
+ } else
+ {
+ Assert.Throws(() => doc.SetCultureName(defaultCultureContentName, defaultCulture));
+ Assert.Throws(() => doc.SetCultureName(nlContentName, nlCulture));
+ }
+
+ ServiceContext.ContentService.Save(doc);
+ doc = ServiceContext.ContentService.GetById(doc.Id);
+
+ AssertAll();
+
+ // Change variation
+ contentType.Variations = contentTypeVariationTo;
+ ServiceContext.ContentService.Save(doc);
+ doc = ServiceContext.ContentService.GetById(doc.Id);
+
+ AssertAll();
+
+ void AssertAll()
+ {
+ if (includeCultureNames)
+ {
+ // Invariant content name is not preserved when content type is set to culture
+ Assert.AreEqual(defaultCultureContentName, doc.Name);
+ Assert.AreEqual(doc.Name, doc.GetCultureName(defaultCulture));
+ Assert.AreEqual(nlContentName, doc.GetCultureName(nlCulture));
+ }
+ else
+ {
+ Assert.AreEqual(invariantContentName, doc.Name);
+ Assert.AreEqual(null, doc.GetCultureName(defaultCulture));
+ Assert.AreEqual(null, doc.GetCultureName(nlCulture));
+ }
+ }
+ }
+
+ [TestCase(ContentVariation.Nothing, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.Segment)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Segment)]
+ [TestCase(ContentVariation.Culture, ContentVariation.CultureAndSegment)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Segment)]
+ [TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Culture)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.CultureAndSegment)]
+ public void Verify_If_Property_Type_Variation_Is_Correctly_Corrected_When_Content_Type_Is_Updated(ContentVariation contentTypeVariation, ContentVariation propertyTypeVariation)
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+
+ // We test an updated content type so it has to be saved first.
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ // Update it
+ contentType.Variations = contentTypeVariation;
+ var properties = CreatePropertyCollection(("title", propertyTypeVariation));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
- //change the property type to be variant
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
-
- //Cannot change a property type to be variant if the content type itself is not variant
- Assert.Throws(() => ServiceContext.ContentTypeService.Save(contentType));
+ // Check if property type variations have been updated correctly
+ Assert.AreEqual(properties.First().Variations, contentTypeVariation & propertyTypeVariation);
}
- [Test]
- public void Change_Property_Type_From_Invariant_Variant()
+ [TestCase(ContentVariation.Nothing, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)]
+ public void Change_Property_Type_From_Invariant_Variant(ContentVariation invariant, ContentVariation variant)
{
var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ // content type supports all variations
+ contentType.Variations = ContentVariation.Culture | ContentVariation.Segment;
+ var properties = CreatePropertyCollection(("title", invariant));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -283,7 +405,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(doc.Edited);
//change the property type to be variant
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ contentType.PropertyTypes.First().Variations = variant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -292,7 +414,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(doc.Edited);
//change back property type to be invariant
- contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
+ contentType.PropertyTypes.First().Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -301,13 +423,17 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(doc.Edited);
}
- [Test]
- public void Change_Property_Type_From_Variant_Invariant()
+ [TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Segment)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
+ public void Change_Property_Type_From_Variant_Invariant(ContentVariation variant, ContentVariation invariant)
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ // content type supports all variations
+ contentType.Variations = ContentVariation.Culture | ContentVariation.Segment;
+ var properties = CreatePropertyCollection(("title", variant));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -320,33 +446,37 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
//change the property type to be invariant
- contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
+ contentType.PropertyTypes.First().Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
//change back property type to be variant
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ contentType.PropertyTypes.First().Variations = variant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
}
- [Test]
- public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
+ [TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Segment)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
+ public void Change_Property_Type_From_Variant_Invariant_On_A_Composition(ContentVariation variant, ContentVariation invariant)
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ // content type supports all variations
+ contentType.Variations = ContentVariation.Culture | ContentVariation.Segment;
+ var properties = CreatePropertyCollection(("title", variant));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
- contentType2.Variations = ContentVariation.Culture;
+ contentType2.Variations = contentType.Variations;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
@@ -362,7 +492,7 @@ namespace Umbraco.Tests.Services
ServiceContext.ContentService.Save(doc2);
//change the property type to be invariant
- contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
+ contentType.PropertyTypes.First().Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
@@ -371,7 +501,7 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back property type to be variant
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ contentType.PropertyTypes.First().Variations = variant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
@@ -380,19 +510,22 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
}
- [Test]
- public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
+ [TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Segment)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
+ public void Change_Content_Type_From_Variant_Invariant_On_A_Composition(ContentVariation variant, ContentVariation invariant)
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
+ contentType.Variations = variant;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
- contentType2.Variations = ContentVariation.Culture;
+ contentType2.Variations = contentType.Variations;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
@@ -408,7 +541,7 @@ namespace Umbraco.Tests.Services
ServiceContext.ContentService.Save(doc2);
//change the content type to be invariant
- contentType.Variations = ContentVariation.Nothing;
+ contentType.Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
@@ -417,7 +550,7 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back content type to be variant
- contentType.Variations = ContentVariation.Culture;
+ contentType.Variations = variant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
@@ -693,22 +826,25 @@ namespace Umbraco.Tests.Services
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':");
}
- [Test]
- public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized()
+ [TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Culture, ContentVariation.Segment)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
+ [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
+ public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized(ContentVariation variant, ContentVariation invariant)
{
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
CreateFrenchAndEnglishLangs();
- var contentType = CreateContentType(ContentVariation.Culture);
+ var contentType = CreateContentType(ContentVariation.Culture | ContentVariation.Segment);
- var properties = CreatePropertyCollection(("value1", ContentVariation.Culture));
+ var properties = CreatePropertyCollection(("value1", variant));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
- var document = (IContent)new Content("document", -1, contentType);
+ IContent document = new Content("document", -1, contentType);
document.SetCultureName("doc1en", "en");
document.SetCultureName("doc1fr", "fr");
document.SetValue("value1", "v1en-init", "en");
@@ -737,7 +873,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(document.Edited);
// switch property type to Invariant
- contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
document = ServiceContext.ContentService.GetById(document.Id);
@@ -764,7 +900,7 @@ namespace Umbraco.Tests.Services
Assert.IsFalse(document.Edited);
// switch property back to Culture
- contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = variant;
ServiceContext.ContentTypeService.Save(contentType);
document = ServiceContext.ContentService.GetById(document.Id);
@@ -793,17 +929,20 @@ namespace Umbraco.Tests.Services
Assert.IsFalse(document.Edited);
}
- [Test]
- public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized()
+ [TestCase(ContentVariation.Nothing, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)]
+ [TestCase(ContentVariation.Segment, ContentVariation.Culture)]
+ [TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)]
+ public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized(ContentVariation invariant, ContentVariation variant)
{
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
CreateFrenchAndEnglishLangs();
- var contentType = CreateContentType(ContentVariation.Culture);
+ var contentType = CreateContentType(ContentVariation.Culture | ContentVariation.Segment);
- var properties = CreatePropertyCollection(("value1", ContentVariation.Nothing));
+ var properties = CreatePropertyCollection(("value1", invariant));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -833,7 +972,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(document.Edited);
// switch property type to Culture
- contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = variant;
ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
document = ServiceContext.ContentService.GetById(document.Id);
@@ -858,7 +997,7 @@ namespace Umbraco.Tests.Services
Assert.IsFalse(document.Edited);
// switch property back to Invariant
- contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType);
document = ServiceContext.ContentService.GetById(document.Id);
diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs
index 340ada2519..57d6b67af8 100644
--- a/src/Umbraco.Tests/Services/MemberServiceTests.cs
+++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs
@@ -96,8 +96,7 @@ namespace Umbraco.Tests.Services
var properties = pmember.Properties.ToList();
- for (var i = 0; i < aliases.Length; i++)
- Assert.AreEqual(properties[i].Alias, aliases[i]);
+ Assert.IsTrue(properties.Select(x => x.Alias).ContainsAll(aliases));
var email = properties[aliases.IndexOf("Email")];
Assert.AreEqual("xemail", email.GetSourceValue());
diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs
index dc8e35bb52..dad3ec4fc0 100644
--- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs
@@ -329,6 +329,11 @@ namespace Umbraco.Tests.TestHelpers
{
throw new NotImplementedException();
}
+
+ public IReadOnlyDictionary> GetReferences(int id)
+ {
+ throw new NotImplementedException();
+ }
}
#endregion
diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
index 9cc2b97445..050de4fcb9 100644
--- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
@@ -259,7 +259,7 @@ namespace Umbraco.Tests.TestHelpers
var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor);
var variationContextAccessor = new TestVariationContextAccessor();
- var service = new PublishedSnapshotService(
+ var service = new XmlPublishedSnapshotService(
ServiceContext,
Factory.GetInstance(),
ScopeProvider,
@@ -357,14 +357,14 @@ namespace Umbraco.Tests.TestHelpers
protected UmbracoContext GetUmbracoContext(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null, IEnumerable mediaUrlProviders = null, IGlobalSettings globalSettings = null, IPublishedSnapshotService snapshotService = null)
{
// ensure we have a PublishedCachesService
- var service = snapshotService ?? PublishedSnapshotService as PublishedSnapshotService;
+ var service = snapshotService ?? PublishedSnapshotService as XmlPublishedSnapshotService;
if (service == null)
throw new Exception("Not a proper XmlPublishedCache.PublishedCachesService.");
- if (service is PublishedSnapshotService)
+ if (service is XmlPublishedSnapshotService)
{
// re-initialize PublishedCacheService content with an Xml source with proper template id
- ((PublishedSnapshotService)service).XmlStore.GetXmlDocument = () =>
+ ((XmlPublishedSnapshotService)service).XmlStore.GetXmlDocument = () =>
{
var doc = new XmlDocument();
doc.LoadXml(GetXmlContent(templateId));
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index fcf73fbffa..39826fcc38 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -511,7 +511,7 @@
-
+
diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs
index 5c291c9601..9ca17675af 100644
--- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs
+++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs
@@ -29,7 +29,7 @@ namespace Umbraco.Tests.Web.Mvc
[UmbracoTest(WithApplication = true)]
public class UmbracoViewPageTests : UmbracoTestBase
{
- private PublishedSnapshotService _service;
+ private XmlPublishedSnapshotService _service;
[TearDown]
public override void TearDown()
@@ -421,7 +421,7 @@ namespace Umbraco.Tests.Web.Mvc
var scopeProvider = TestObjects.GetScopeProvider(Mock.Of());
var factory = Mock.Of();
var umbracoContextAccessor = Mock.Of();
- _service = new PublishedSnapshotService(svcCtx, factory, scopeProvider, cache,
+ _service = new XmlPublishedSnapshotService(svcCtx, factory, scopeProvider, cache,
null, null,
umbracoContextAccessor, null, null, null,
new TestDefaultCultureAccessor(),
diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js
index 819c804a4f..acf0e6a8f0 100644
--- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js
+++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js
@@ -42,6 +42,11 @@ gulp.task('dependencies', function () {
"src": ["./node_modules/angular/angular.js"],
"base": "./node_modules/angular"
},
+ {
+ "name": "angular-aria",
+ "src": ["./node_modules/angular-aria/angular-aria.min.js"],
+ "base": "./node_modules/angular-aria"
+ },
{
"name": "angular-cookies",
"src": ["./node_modules/angular-cookies/angular-cookies.js"],
@@ -100,7 +105,7 @@ gulp.task('dependencies', function () {
"name": "angular-messages",
"src": ["./node_modules/angular-messages/angular-messages.js"],
"base": "./node_modules/angular-messages"
- },
+ },
{
"name": "angular-mocks",
"src": ["./node_modules/angular-mocks/angular-mocks.js"],
diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less
index f88b66bd18..ff1f96296a 100644
--- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less
+++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/tables.less
@@ -241,3 +241,7 @@ table th[class*="span"],
background-color: darken(@infoBackground, 5%);
}
}
+
+.table .icon {
+ vertical-align: bottom;
+}
diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json
index 9c0011d2f5..6c05976c1d 100644
--- a/src/Umbraco.Web.UI.Client/package-lock.json
+++ b/src/Umbraco.Web.UI.Client/package-lock.json
@@ -916,6 +916,11 @@
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz",
"integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ=="
},
+ "angular-aria": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.5.tgz",
+ "integrity": "sha512-X2dGRw+PK7hrV7/X1Ns4e5P3KC/OBFi1l7z//D/v7zbZObsAx48qBoX7unsck+s4+mnO+ikNNkHG5N49VfAyRw=="
+ },
"angular-chart.js": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/angular-chart.js/-/angular-chart.js-1.1.1.tgz",
@@ -5302,8 +5307,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"aproba": {
"version": "1.2.0",
@@ -5324,14 +5328,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -5346,20 +5348,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -5476,8 +5475,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"ini": {
"version": "1.3.5",
@@ -5489,7 +5487,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -5504,7 +5501,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -5512,14 +5508,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -5538,7 +5532,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -5619,8 +5612,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -5632,7 +5624,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"wrappy": "1"
}
@@ -5718,8 +5709,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -5755,7 +5745,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -5775,7 +5764,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -5819,14 +5807,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
}
}
},
@@ -8616,9 +8602,9 @@
}
},
"lodash": {
- "version": "4.17.11",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
- "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
+ "version": "4.17.13",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.13.tgz",
+ "integrity": "sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA==",
"dev": true
},
"lodash._basecopy": {
@@ -8956,9 +8942,9 @@
}
},
"marked": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/marked/-/marked-0.5.2.tgz",
- "integrity": "sha512-fdZvBa7/vSQIZCi4uuwo2N3q+7jJURpMVCcbaX0S1Mg65WZ5ilXvC67MviJAsdjqqgD+CEq4RKo5AYGgINkVAA==",
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.3.tgz",
+ "integrity": "sha512-Fqa7eq+UaxfMriqzYLayfqAE40WN03jf+zHjT18/uXNuzjq3TY0XTbrAoPeqSJrAmPz11VuUA+kBPYOhHt9oOQ==",
"dev": true
},
"matchdep": {
@@ -9356,9 +9342,9 @@
"dev": true
},
"nouislider": {
- "version": "14.0.1",
- "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.0.1.tgz",
- "integrity": "sha512-YNLKuABWYxmC5WXJ9TUj3N7+iyL/xT3+jm1mgOMXoqBhAL0Pj9BMgyKmLgwRnrxNN+C/fe7sFmpQDDPsxbMT2w=="
+ "version": "14.0.2",
+ "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.0.2.tgz",
+ "integrity": "sha512-N4AQStV4frh+XcLUwMI/hZpBP6tRboDE/4LZ7gzfxMVXFi/2J9URphnm40Ff4KEyrAVGSGaWApvljoMzTNWBlA=="
},
"npm": {
"version": "6.11.3",
diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json
index c8acd0f88d..ae1dbcd828 100644
--- a/src/Umbraco.Web.UI.Client/package.json
+++ b/src/Umbraco.Web.UI.Client/package.json
@@ -10,6 +10,7 @@
"ace-builds": "1.4.2",
"angular": "1.7.5",
"angular-animate": "1.7.5",
+ "angular-aria": "1.7.5",
"angular-chart.js": "^1.1.1",
"angular-cookies": "1.7.5",
"angular-dynamic-locale": "0.1.37",
diff --git a/src/Umbraco.Web.UI.Client/src/app.js b/src/Umbraco.Web.UI.Client/src/app.js
index 8e0eaa4943..74a7008901 100644
--- a/src/Umbraco.Web.UI.Client/src/app.js
+++ b/src/Umbraco.Web.UI.Client/src/app.js
@@ -12,6 +12,7 @@ var app = angular.module('umbraco', [
'ngSanitize',
'ngTouch',
'ngMessages',
+ 'ngAria',
'tmh.dynamicLocale',
'ngFileUpload',
'LocalStorageModule',
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 2a2852c24f..5398038113 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
@@ -4,7 +4,7 @@
function ContentEditController($rootScope, $scope, $routeParams, $q, $window,
appState, contentResource, entityResource, navigationService, notificationsService,
serverValidationManager, contentEditingHelper, localizationService, formHelper, umbRequestHelper,
- editorState, $http, eventsService, overlayService, $location, localStorageService) {
+ editorState, $http, eventsService, overlayService, $location, localStorageService, treeService) {
var evts = [];
var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode;
@@ -201,6 +201,12 @@
$scope.page.buttonGroupState = "success";
}));
+ evts.push(eventsService.on("rte.shortcut.save", function(){
+ if ($scope.page.showSaveButton) {
+ $scope.save();
+ }
+ }));
+
evts.push(eventsService.on("content.saved", function(){
// Clear out localstorage keys that start with tinymce__
// When we save/perist a content node
@@ -305,7 +311,7 @@
}
/** Syncs the content item to it's tree node - this occurs on first load and after saving */
- function syncTreeNode(content, path, initialLoad) {
+ function syncTreeNode(content, path, initialLoad, reloadChildren) {
if (infiniteMode || !path) {
return;
@@ -315,6 +321,9 @@
navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true })
.then(function (syncArgs) {
$scope.page.menu.currentNode = syncArgs.node;
+ if (reloadChildren && syncArgs.node.expanded) {
+ treeService.loadNodeChildren({node: syncArgs.node});
+ }
}, function () {
//handle the rejection
console.log("A problem occurred syncing the tree! A path is probably incorrect.")
@@ -446,7 +455,7 @@
//needs to be manually set for infinite editing mode
$scope.page.isNew = false;
- syncTreeNode($scope.content, data.path);
+ syncTreeNode($scope.content, data.path, false, args.reloadChildren);
eventsService.emit("content.saved", { content: $scope.content, action: args.action });
@@ -851,7 +860,8 @@
return contentResource.publishWithDescendants(content, create, model.includeUnpublished, files, showNotifications);
},
action: "publishDescendants",
- showNotifications: false
+ showNotifications: false,
+ reloadChildren: model.includeUnpublished
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js
index 8560b5a3ea..0f3d251e82 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js
@@ -26,6 +26,7 @@
@param {string} value Set the value of the checkbox.
@param {string} name Set the name of the checkbox.
@param {string} text Set the text for the checkbox label.
+@param {string} labelKey Set a dictinary/localization string for the checkbox label
@param {string} serverValidationField Set the val-server-field of the checkbox.
@param {boolean} disabled Set the checkbox to be disabled.
@param {boolean} required Set the checkbox to be required.
@@ -35,13 +36,25 @@
(function () {
'use strict';
-
- function UmbCheckboxController($timeout) {
-
+
+ function UmbCheckboxController($timeout, localizationService) {
+
var vm = this;
+ vm.$onInit = onInit;
vm.change = change;
+ function onInit() {
+ // If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [
+ if (vm.labelKey) {
+ localizationService.localize(vm.labelKey).then(function (data) {
+ if(data.indexOf('[') === -1){
+ vm.text = data;
+ }
+ });
+ }
+ }
+
function change() {
if (vm.onChange) {
$timeout(function () {
@@ -50,7 +63,7 @@
}
}
}
-
+
var component = {
templateUrl: 'views/components/forms/umb-checkbox.html',
controller: UmbCheckboxController,
@@ -61,6 +74,7 @@
value: "@",
name: "@",
text: "@",
+ labelKey: "@?",
serverValidationField: "@",
disabled: "<",
required: "<",
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js
index df45181991..bb36e3b027 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js
@@ -22,6 +22,7 @@
@param {boolean} model Set to true or false to set the radiobutton to checked or unchecked.
+@param {string} inputId Set the id of the radiobutton.
@param {string} value Set the value of the radiobutton.
@param {string} name Set the name of the radiobutton.
@param {string} text Set the text for the radiobutton label.
@@ -55,6 +56,7 @@
controllerAs: 'vm',
bindings: {
model: "=",
+ inputId: "@",
value: "@",
name: "@",
text: "@",
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbboxheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbboxheader.directive.js
index a0fbd2c24a..21f0c33de5 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbboxheader.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbbox/umbboxheader.directive.js
@@ -45,7 +45,7 @@ Use this directive to construct a title. Recommended to use it inside an {@link
(function(){
'use strict';
- function BoxHeaderDirective() {
+ function BoxHeaderDirective(localizationService) {
var directive = {
restrict: 'E',
@@ -57,6 +57,26 @@ Use this directive to construct a title. Recommended to use it inside an {@link
title: "@?",
descriptionKey: "@?",
description: "@?"
+ },
+ link: function (scope) {
+
+ scope.titleLabel = scope.title;
+
+ if (scope.titleKey) {
+ localizationService.localize(scope.titleKey, [], scope.title).then((data) => {
+ scope.titleLabel = data;
+ });
+
+ }
+
+ scope.descriptionLabel = scope.description;
+
+ if (scope.descriptionKey) {
+ localizationService.localize(scope.descriptionKey, [], scope.description).then((data) => {
+ scope.descriptionLabel = data;
+ });
+
+ }
}
};
@@ -66,4 +86,4 @@ Use this directive to construct a title. Recommended to use it inside an {@link
angular.module('umbraco.directives').directive('umbBoxHeader', BoxHeaderDirective);
-})();
\ No newline at end of file
+})();
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js
index d998504d1b..9d7927f59a 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js
@@ -18,6 +18,7 @@ Use this directive to generate color swatches to pick from.
@param {string} size (attribute): The size (s, m).
@param {string} useLabel (attribute): Specify if labels should be used.
@param {string} useColorClass (attribute): Specify if color values are css classes.
+@param {string} colorClassNamePrefix (attribute): Specify the prefix used for the class for each color (defaults to "btn").
@param {function} onSelect (expression): Callback function when the item is selected.
**/
@@ -32,6 +33,11 @@ Use this directive to generate color swatches to pick from.
if (angular.isUndefined(scope.useColorClass)) {
scope.useColorClass = false;
}
+
+ // Set default to "btn" if not defined
+ if (angular.isUndefined(scope.colorClassNamePrefix)) {
+ scope.colorClassNamePrefix = "btn";
+ }
scope.setColor = function (color, $index, $event) {
if (scope.onSelect) {
@@ -66,7 +72,8 @@ Use this directive to generate color swatches to pick from.
selectedColor: '=',
onSelect: '&',
useLabel: '=',
- useColorClass: '=?'
+ useColorClass: '=?',
+ colorClassNamePrefix: '@?'
},
link: link
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js
index ce6b90c1ec..1ddd09357a 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js
@@ -55,7 +55,8 @@ function confirmDirective() {
onConfirm: '=',
onCancel: '=',
caption: '@',
- confirmButtonStyle: '@'
+ confirmButtonStyle: '@',
+ confirmLabelKey: '@'
},
link: function (scope, element, attr, ctrl) {
scope.showCancel = false;
diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js
new file mode 100644
index 0000000000..de340bab27
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js
@@ -0,0 +1,20 @@
+/**
+ * @ngdoc filter
+ * @name umbraco.filters.filter:CMS_joinArray
+ * @namespace umbCmsJoinArray
+ *
+ * param {array} array of string or objects, if an object use the third argument to specify which prop to list.
+ * param {seperator} string containing the seperator to add between joined values.
+ * param {prop} string used if joining an array of objects, set the name of properties to join.
+ *
+ * @description
+ * Join an array of string or an array of objects, with a costum seperator.
+ *
+ */
+angular.module("umbraco.filters").filter('umbCmsJoinArray', function () {
+ return function join(array, separator, prop) {
+ return (!angular.isUndefined(prop) ? array.map(function (item) {
+ return item[prop];
+ }) : array).join(separator || '');
+ };
+});
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js
index 7c4daedd38..7aedfccacf 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js
@@ -43,6 +43,30 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) {
"Failed to retrieve pre values for editor alias " + editorAlias);
},
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.dataTypeResource#getReferences
+ * @methodOf umbraco.resources.dataTypeResource
+ *
+ * @description
+ * Retrieves references of a given data type.
+ *
+ * @param {Int} id id of datatype to retrieve references for
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ getReferences: function (id) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "dataTypeApiBaseUrl",
+ "GetReferences",
+ { id: id })),
+ "Failed to retrieve usages for data type of id " + id);
+
+ },
+
/**
* @ngdoc method
* @name umbraco.resources.dataTypeResource#getById
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js
index 73a1651b5e..c9ce7866e5 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js
@@ -105,6 +105,20 @@ function macroResource($q, $http, umbRequestHelper) {
);
},
+ getGroupedParameterEditors: function () {
+ return umbRequestHelper.resourcePromise(
+ $http.get(umbRequestHelper.getApiUrl("macroApiBaseUrl", "GetGroupedParameterEditors"),
+ "Failed to get parameter editors")
+ );
+ },
+
+ getParameterEditorByAlias: function(alias) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(umbRequestHelper.getApiUrl("macroApiBaseUrl", "GetParameterEditorByAlias", { "alias": alias }),
+ "Failed to get parameter editor")
+ );
+ },
+
getById: function(id) {
return umbRequestHelper.resourcePromise(
$http.get(umbRequestHelper.getApiUrl("macroApiBaseUrl", "GetById", { "id": id }), "Failed to get macro")
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
index 4ff5a05522..33a15d74d3 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
@@ -1143,6 +1143,14 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
prependToContext: true
});
+ // the editor frame catches Ctrl+S and handles it with the system save dialog
+ // - we want to handle it in the content controller, so we'll emit an event instead
+ editor.addShortcut('Ctrl+S', '', function () {
+ angularHelper.safeApply($rootScope, function() {
+ eventsService.emit("rte.shortcut.save");
+ });
+ });
+
},
insertLinkInEditor: function (editor, target, anchorElm) {
diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js
index cd00e1261d..7d199c5c4f 100644
--- a/src/Umbraco.Web.UI.Client/src/init.js
+++ b/src/Umbraco.Web.UI.Client/src/init.js
@@ -138,14 +138,14 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService',
var toRetain = navigationService.retainQueryStrings(currentRouteParams, next.params);
- //if toRetain is not null it means that there are missing query strings and we need to update the current params
+ //if toRetain is not null it means that there are missing query strings and we need to update the current params.
if (toRetain) {
$route.updateParams(toRetain);
}
//check if the location being changed is only due to global/state query strings which means the location change
//isn't actually going to cause a route change.
- if (!toRetain && navigationService.isRouteChangingNavigation(currentRouteParams, next.params)) {
+ if (navigationService.isRouteChangingNavigation(currentRouteParams, next.params)) {
//The location change will cause a route change, continue the route if the query strings haven't been updated.
$route.reload();
diff --git a/src/Umbraco.Web.UI.Client/src/install.loader.js b/src/Umbraco.Web.UI.Client/src/install.loader.js
index 99be6db9f1..997c8cbe84 100644
--- a/src/Umbraco.Web.UI.Client/src/install.loader.js
+++ b/src/Umbraco.Web.UI.Client/src/install.loader.js
@@ -6,6 +6,7 @@ LazyLoad.js([
'lib/angular-touch/angular-touch.js',
'lib/angular-sanitize/angular-sanitize.js',
'lib/angular-messages/angular-messages.js',
+ 'lib/angular-aria/angular-aria.min.js',
'lib/underscore/underscore-min.js',
'lib/angular-ui-sortable/sortable.js',
'js/installer.app.js',
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less
index 985e57bea5..11194eeb43 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less
@@ -4,6 +4,7 @@
.umb-checkbox-list {
list-style: none;
margin-left: 0;
+ margin-top: 6px;
}
.umb-checkbox-list__item {
@@ -12,6 +13,10 @@
margin-bottom: 2px;
}
+.umb-checkbox-list li:first-child {
+ font-weight: bold;
+}
+
.umb-checkbox-list__item:last-child {
border-bottom: none;
}
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less
index 2df0cc5fd8..5bcfdd1c71 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less
@@ -6,6 +6,10 @@
span.flatpickr-day {
border-radius: @baseBorderRadius;
border: none;
+
+ &.today:not(.active) {
+ border: 1px solid;
+ }
}
span.flatpickr-day:hover {
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less
index 12d7085b0a..94cfa6f62c 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less
@@ -36,6 +36,10 @@ a.umb-list-item:focus {
color: @gray-4;
}
+.umb-list-item__description--checkbox{
+ margin: 0 0 0 26px;
+}
+
.umb-list-checkbox {
position: absolute;
opacity: 0;
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less
index 27b64f85fb..5e766b7578 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less
@@ -27,14 +27,13 @@
}
+.umb-table__action,
.umb-table a {
+ background: transparent;
+ border: 0 none;
text-decoration: none;
- cursor: pointer;
-
- &:focus {
- outline: none;
- text-decoration: none;
- }
+ padding: 0;
+ margin-left: 1px;
}
input.umb-table__input {
@@ -51,20 +50,26 @@ input.umb-table__input {
}
.umb-table-head__link {
+ background: transparent;
+ border: 0 none;
position: relative;
- cursor: default;
text-decoration: none;
color: @gray-3;
+ font-size: inherit;
+ font-weight: inherit;
+ padding: 0 1px;
+
&:hover {
text-decoration: none;
+ cursor: default;
color: @gray-3;
}
}
-.umb-table-head__link .sortable {
+.umb-table-head__link.sortable {
+ cursor: pointer;
&:hover {
text-decoration: none;
- cursor: pointer;
color: @black;
}
}
@@ -136,15 +141,15 @@ input.umb-table__input {
}
.umb-table-body__link {
-
color: @ui-option-type;
font-size: 14px;
font-weight: bold;
text-decoration: none;
-
+
&:hover, &:focus {
color: @ui-option-type-hover;
text-decoration: underline;
+ outline: none;
}
}
@@ -155,6 +160,7 @@ input.umb-table__input {
font-size: 20px;
line-height: 20px;
color: @ui-option-type;
+ vertical-align: bottom;
}
.umb-table-body__checkicon,
@@ -240,7 +246,7 @@ input.umb-table__input {
.umb-table-cell {
display: flex;
flex-flow: row nowrap;
- flex: 1 1 1%; //NOTE 1% is a Internet Explore hack, so that cells don't collapse
+ flex: 1 1 5%;
position: relative;
margin: auto 14px;
padding: 6px 2px;
@@ -253,6 +259,11 @@ input.umb-table__input {
white-space: nowrap; //NOTE Disable/Enable this to keep textstring on one line
text-overflow: ellipsis;
}
+.umb-table-cell.--noOverflow > * {
+ overflow: visible;
+ white-space: normal;
+ text-overflow: unset;
+}
.umb-table-cell:first-of-type:not(.not-fixed) {
flex: 0 0 25px;
@@ -264,6 +275,9 @@ input.umb-table__input {
flex: 0 0 auto !important;
}
+.umb-table-cell--nano {
+ flex: 0 0 50px;
+}
.umb-table-cell--small {
flex: .5 .5 1%;
max-width: 12.5%;
@@ -280,8 +294,8 @@ input.umb-table__input {
// Increases the space for the name cell
.umb-table__name {
- flex: 1 1 25%;
- max-width: 25%;
+ flex: 1 1 20%;
+ max-width: 300px;
}
.umb-table__loading-overlay {
diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less
index 0b6d1b7a60..3ead4d6905 100644
--- a/src/Umbraco.Web.UI.Client/src/less/hacks.less
+++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less
@@ -111,11 +111,10 @@ iframe, .content-column-body {
}
.pa-select-type label {
- padding: 0 20px;
+ padding: 0 15px;
}
.pa-access-header {
- font-weight: bold;
margin: 0 0 3px 0;
padding-bottom: 0;
}
diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less
index e14bb5c0d6..152ea49bbd 100644
--- a/src/Umbraco.Web.UI.Client/src/less/properties.less
+++ b/src/Umbraco.Web.UI.Client/src/less/properties.less
@@ -48,6 +48,10 @@
flex-direction: row;
}
+.date-wrapper-mini--checkbox{
+ margin: 0 0 0 26px;
+}
+
.date-wrapper-mini__date {
display: flex;
diff --git a/src/Umbraco.Web.UI.Client/src/less/tables.less b/src/Umbraco.Web.UI.Client/src/less/tables.less
index cd6304ef49..2ecfa4d04e 100644
--- a/src/Umbraco.Web.UI.Client/src/less/tables.less
+++ b/src/Umbraco.Web.UI.Client/src/less/tables.less
@@ -62,7 +62,7 @@ table {
}
-.table tr > td:first-child {
+.table:not(.table-bordered) tr > td:first-child {
border-left: 4px solid transparent;
}
.table tr.--selected > td:first-child {
@@ -263,3 +263,15 @@ table th[class*="span"],
.table-sortable tbody tr {
cursor: move;
}
+
+.table__action-overlay{
+ background: transparent;
+ border: 0 none;
+ padding: 0;
+ font-style: italic;
+
+ &:focus,
+ &:hover{
+ text-decoration: underline;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html
index bb0804e2bf..071d093ab4 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html
@@ -62,7 +62,7 @@