diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs
index e8bd53e9c6..882aa86b4e 100644
--- a/src/Umbraco.Core/Services/EntityService.cs
+++ b/src/Umbraco.Core/Services/EntityService.cs
@@ -10,7 +10,7 @@ using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Services
{
- internal class EntityService : IService
+ public class EntityService : IService
{
private readonly IDatabaseUnitOfWorkProvider _uowProvider;
private readonly RepositoryFactory _repositoryFactory;
diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs
new file mode 100644
index 0000000000..73929eed31
--- /dev/null
+++ b/src/Umbraco.Core/Services/RelationService.cs
@@ -0,0 +1,407 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.EntityBase;
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.Persistence.UnitOfWork;
+
+namespace Umbraco.Core.Services
+{
+ public class RelationService
+ {
+ private readonly IDatabaseUnitOfWorkProvider _uowProvider;
+ private readonly RepositoryFactory _repositoryFactory;
+ private readonly EntityService _entityService;
+
+ public RelationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory,
+ EntityService entityService)
+ {
+ _uowProvider = uowProvider;
+ _repositoryFactory = repositoryFactory;
+ _entityService = entityService;
+ }
+
+ ///
+ /// Gets a by its Id
+ ///
+ /// Id of the
+ /// A object
+ public Relation GetById(int id)
+ {
+ using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
+ {
+ return repository.Get(id);
+ }
+ }
+
+ ///
+ /// Gets a by its Id
+ ///
+ /// Id of the
+ /// A object
+ public RelationType GetRelationTypeById(int id)
+ {
+ using (var repository = _repositoryFactory.CreateRelationTypeRepository(_uowProvider.GetUnitOfWork()))
+ {
+ return repository.Get(id);
+ }
+ }
+
+ ///
+ /// Gets all objects
+ ///
+ /// Optional array of integer ids to return relations for
+ /// An enumerable list of objects
+ public IEnumerable GetAllRelations(params int[] ids)
+ {
+ using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
+ {
+ return repository.GetAll(ids);
+ }
+ }
+
+ ///
+ /// Gets all objects by their
+ ///
+ /// to retrieve Relations for
+ /// An enumerable list of objects
+ public IEnumerable GetAllRelationsByRelationType(RelationType relationType)
+ {
+ return GetAllRelationsByRelationType(relationType.Id);
+ }
+
+ ///
+ /// Gets all objects by their 's Id
+ ///
+ /// Id of the to retrieve Relations for
+ /// An enumerable list of objects
+ public IEnumerable GetAllRelationsByRelationType(int relationTypeId)
+ {
+ using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
+ {
+ var query = new Query().Where(x => x.RelationTypeId == relationTypeId);
+ return repository.GetByQuery(query);
+ }
+ }
+
+ ///
+ /// Gets all objects
+ ///
+ /// Optional array of integer ids to return relationtypes for
+ /// An enumerable list of objects
+ public IEnumerable GetAllRelationTypes(params int[] ids)
+ {
+ using (var repository = _repositoryFactory.CreateRelationTypeRepository(_uowProvider.GetUnitOfWork()))
+ {
+ return repository.GetAll(ids);
+ }
+ }
+
+ ///
+ /// Gets a list of objects by their parent Id
+ ///
+ /// Id of the parent to retrieve relations for
+ /// An enumerable list of objects
+ public IEnumerable GetByParentId(int id)
+ {
+ using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
+ {
+ var query = new Query().Where(x => x.ParentId == id);
+ return repository.GetByQuery(query);
+ }
+ }
+
+ ///
+ /// Gets a list of objects by their child Id
+ ///
+ /// Id of the child to retrieve relations for
+ /// An enumerable list of objects
+ public IEnumerable GetByChildId(int id)
+ {
+ using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
+ {
+ var query = new Query().Where(x => x.ChildId == id);
+ return repository.GetByQuery(query);
+ }
+ }
+
+ ///
+ /// Gets a list of objects by the Name of the
+ ///
+ /// Name of the to retrieve Relations for
+ /// An enumerable list of objects
+ public IEnumerable GetByRelationTypeName(string relationTypeName)
+ {
+ List relationTypeIds = null;
+ using (var repository = _repositoryFactory.CreateRelationTypeRepository(_uowProvider.GetUnitOfWork()))
+ {
+ var query = new Query().Where(x => x.Name == relationTypeName);
+ var relationTypes = repository.GetByQuery(query);
+ if (relationTypes.Any())
+ {
+ relationTypeIds = relationTypes.Select(x => x.Id).ToList();
+ }
+ }
+
+ if (relationTypeIds == null)
+ return Enumerable.Empty();
+
+ return GetRelationsByListOfTypeIds(relationTypeIds);
+ }
+
+ ///
+ /// Gets a list of objects by the Alias of the
+ ///
+ /// Alias of the to retrieve Relations for
+ /// An enumerable list of objects
+ public IEnumerable GetByRelationTypeAlias(string relationTypeAlias)
+ {
+ List relationTypeIds = null;
+ using (var repository = _repositoryFactory.CreateRelationTypeRepository(_uowProvider.GetUnitOfWork()))
+ {
+ var query = new Query().Where(x => x.Alias == relationTypeAlias);
+ var relationTypes = repository.GetByQuery(query);
+ if (relationTypes.Any())
+ {
+ relationTypeIds = relationTypes.Select(x => x.Id).ToList();
+ }
+ }
+
+ if (relationTypeIds == null)
+ return Enumerable.Empty();
+
+ return GetRelationsByListOfTypeIds(relationTypeIds);
+ }
+
+ ///
+ /// Gets a list of objects by the Id of the
+ ///
+ /// Id of the to retrieve Relations for
+ /// An enumerable list of objects
+ public IEnumerable GetByRelationTypeId(int relationTypeId)
+ {
+ using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
+ {
+ var query = new Query().Where(x => x.RelationTypeId == relationTypeId);
+ return repository.GetByQuery(query);
+ }
+ }
+
+ ///
+ /// Gets the Child object from a Relation as an
+ ///
+ /// Relation to retrieve child object from
+ /// Optional bool to load the complete object graph when set to False
+ /// An
+ public IUmbracoEntity GetChildEntityFromRelation(Relation relation, bool loadBaseType = false)
+ {
+ var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
+ return _entityService.Get(relation.ChildId, objectType, loadBaseType);
+ }
+
+ ///
+ /// Gets the Parent object from a Relation as an
+ ///
+ /// Relation to retrieve parent object from
+ /// Optional bool to load the complete object graph when set to False
+ /// An
+ public IUmbracoEntity GetParentEntityFromRelation(Relation relation, bool loadBaseType = false)
+ {
+ var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
+ return _entityService.Get(relation.ParentId, objectType, loadBaseType);
+ }
+
+ ///
+ /// Gets the Parent and Child objects from a Relation as a "/> with .
+ ///
+ /// Relation to retrieve parent and child object from
+ /// Optional bool to load the complete object graph when set to False
+ /// Returns a Tuple with Parent (item1) and Child (item2)
+ public Tuple GetEntitiesFromRelation(Relation relation, bool loadBaseType = false)
+ {
+ var childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
+ var parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
+
+ var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType);
+ var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType);
+
+ return new Tuple(parent, child);
+ }
+
+ ///
+ /// Gets the Child objects from a list of Relations as a list of objects.
+ ///
+ /// List of relations to retrieve child objects from
+ /// Optional bool to load the complete object graph when set to False
+ /// An enumerable list of
+ public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false)
+ {
+ foreach (var relation in relations)
+ {
+ var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
+ yield return _entityService.Get(relation.ChildId, objectType, loadBaseType);
+ }
+ }
+
+ ///
+ /// Gets the Parent objects from a list of Relations as a list of objects.
+ ///
+ /// List of relations to retrieve parent objects from
+ /// Optional bool to load the complete object graph when set to False
+ /// An enumerable list of
+ public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations,
+ bool loadBaseType = false)
+ {
+ foreach (var relation in relations)
+ {
+ var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
+ yield return _entityService.Get(relation.ParentId, objectType, loadBaseType);
+ }
+ }
+
+ ///
+ /// Gets the Parent and Child objects from a list of Relations as a list of objects.
+ ///
+ /// List of relations to retrieve parent and child objects from
+ /// Optional bool to load the complete object graph when set to False
+ /// An enumerable list of with
+ public IEnumerable> GetEntitiesFromRelations(
+ IEnumerable relations,
+ bool loadBaseType = false)
+ {
+ foreach (var relation in relations)
+ {
+ var childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
+ var parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
+
+ var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType);
+ var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType);
+
+ yield return new Tuple(parent, child);
+ }
+ }
+
+ ///
+ /// Checks whether any relations exists for the passed in .
+ ///
+ /// to check for relations
+ /// Returns True if any relations exists for the given , otherwise False
+ public bool HasRelations(RelationType relationType)
+ {
+ using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
+ {
+ var query = new Query().Where(x => x.RelationTypeId == relationType.Id);
+ return repository.GetByQuery(query).Any();
+ }
+ }
+
+ ///
+ /// Checks whether any relations exists for the passed in Id.
+ ///
+ /// Id of an object to check relations for
+ /// Returns True if any relations exists with the given Id, otherwise False
+ public bool IsRelated(int id)
+ {
+ using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
+ {
+ var query = new Query().Where(x => x.ParentId == id || x.ChildId == id);
+ return repository.GetByQuery(query).Any();
+ }
+ }
+
+ ///
+ /// Saves a
+ ///
+ /// Relation to save
+ public void Save(Relation relation)
+ {
+ var uow = _uowProvider.GetUnitOfWork();
+ using (var repository = _repositoryFactory.CreateRelationRepository(uow))
+ {
+ repository.AddOrUpdate(relation);
+ uow.Commit();
+ }
+ }
+
+ ///
+ /// Saves a
+ ///
+ /// RelationType to Save
+ public void Save(RelationType relationType)
+ {
+ var uow = _uowProvider.GetUnitOfWork();
+ using (var repository = _repositoryFactory.CreateRelationTypeRepository(uow))
+ {
+ repository.AddOrUpdate(relationType);
+ uow.Commit();
+ }
+ }
+
+ ///
+ /// Deletes a
+ ///
+ /// Relation to Delete
+ public void Delete(Relation relation)
+ {
+ var uow = _uowProvider.GetUnitOfWork();
+ using (var repository = _repositoryFactory.CreateRelationRepository(uow))
+ {
+ repository.Delete(relation);
+ uow.Commit();
+ }
+ }
+
+ ///
+ /// Deletes a
+ ///
+ /// RelationType to Delete
+ public void Delete(RelationType relationType)
+ {
+ var uow = _uowProvider.GetUnitOfWork();
+ using (var repository = _repositoryFactory.CreateRelationTypeRepository(uow))
+ {
+ repository.Delete(relationType);
+ uow.Commit();
+ }
+ }
+
+ ///
+ /// Deletes all objects based on the passed in
+ ///
+ /// to Delete Relations for
+ public void DeleteRelationsOfType(RelationType relationType)
+ {
+ var uow = _uowProvider.GetUnitOfWork();
+ using (var repository = _repositoryFactory.CreateRelationRepository(uow))
+ {
+ var query = new Query().Where(x => x.RelationTypeId == relationType.Id);
+ var list = repository.GetByQuery(query).ToList();
+
+ foreach (var relation in list)
+ {
+ repository.Delete(relation);
+ }
+ uow.Commit();
+ }
+ }
+
+ #region Private Methods
+ private IEnumerable GetRelationsByListOfTypeIds(IEnumerable relationTypeIds)
+ {
+ var relations = new List();
+ using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
+ {
+ foreach (var relationTypeId in relationTypeIds)
+ {
+ int id = relationTypeId;
+ var query = new Query().Where(x => x.RelationTypeId == id);
+ relations.AddRange(repository.GetByQuery(query).ToList());
+ }
+ }
+ return relations;
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs
index c5d4079aea..58ff6042f7 100644
--- a/src/Umbraco.Core/Services/ServiceContext.cs
+++ b/src/Umbraco.Core/Services/ServiceContext.cs
@@ -22,6 +22,7 @@ namespace Umbraco.Core.Services
private Lazy _packagingService;
private Lazy _serverRegistrationService;
private Lazy _entityService;
+ private Lazy _relationService;
///
/// Constructor
@@ -78,6 +79,9 @@ namespace Umbraco.Core.Services
if (_entityService == null)
_entityService = new Lazy(() => new EntityService(provider, repositoryFactory.Value, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value));
+
+ if(_relationService == null)
+ _relationService = new Lazy(() => new RelationService(provider, repositoryFactory.Value, _entityService.Value));
}
///
@@ -91,11 +95,19 @@ namespace Umbraco.Core.Services
///
/// Gets the
///
- internal EntityService EntityService
+ public EntityService EntityService
{
get { return _entityService.Value; }
}
+ ///
+ /// Gets the
+ ///
+ public RelationService RelationService
+ {
+ get { return _relationService.Value; }
+ }
+
///
/// Gets the
///
diff --git a/src/Umbraco.Core/Strings/CleanStringType.cs b/src/Umbraco.Core/Strings/CleanStringType.cs
index cda6274db5..28a801aa54 100644
--- a/src/Umbraco.Core/Strings/CleanStringType.cs
+++ b/src/Umbraco.Core/Strings/CleanStringType.cs
@@ -17,7 +17,7 @@ namespace Umbraco.Core.Strings
///
/// Flag mask for casing.
///
- CaseMask = 0x1f, // 0xff - 8 possible values
+ CaseMask = 0x3f, // 0xff - 8 possible values
///
/// Flag mask for encoding.
@@ -59,6 +59,13 @@ namespace Umbraco.Core.Strings
///
UpperCase = 0x10,
+ ///
+ /// Umbraco "safe alias" case.
+ ///
+ /// This is for backward compatibility. Casing is unchanged within terms,
+ /// and is pascal otherwise.
+ UmbracoCase = 0x20,
+
///
/// Unicode encoding.
///
diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs
index 9f34756af8..880c5d110f 100644
--- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs
+++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs
@@ -144,10 +144,23 @@ namespace Umbraco.Core.Strings
}
public Func PreFilter { get; private set; }
+
+ // indicate whether an uppercase within a term eg "fooBar" is to break
+ // into a new term, or to be considered as part of the current term
public bool BreakTermsOnUpper { get; private set; }
+
+ // indicates whether it is legal to have leading digits, or whether they
+ // should be stripped as any other illegal character
public bool AllowLeadingDigits { get; private set; }
+
+ // indicates whether underscore is a valid character in a term or is
+ // to be considered as a separator
public bool AllowUnderscoreInTerm { get; private set; }
+ // indicates whether acronyms parsing is greedy ie whether "FOObar" is
+ // "FOO" + "bar" (greedy) or "FO" + "Obar" (non-greedy)
+ public bool GreedyAcronyms { get { return false; } }
+
public static readonly HelperConfig Empty = new HelperConfig();
}
@@ -239,7 +252,7 @@ function validateSafeAlias(id, value, immediate, callback) {{
///
public virtual string CleanStringForSafeAlias(string text)
{
- return CleanString(text, CleanStringType.Ascii | CleanStringType.CamelCase | CleanStringType.Alias);
+ return CleanString(text, CleanStringType.Ascii | CleanStringType.UmbracoCase | CleanStringType.Alias);
}
///
@@ -253,7 +266,7 @@ function validateSafeAlias(id, value, immediate, callback) {{
///
public virtual string CleanStringForSafeAlias(string text, CultureInfo culture)
{
- return CleanString(text, CleanStringType.Ascii | CleanStringType.CamelCase | CleanStringType.Alias, culture);
+ return CleanString(text, CleanStringType.Ascii | CleanStringType.UmbracoCase | CleanStringType.Alias, culture);
}
///
@@ -720,6 +733,8 @@ function validateSafeAlias(id, value, immediate, callback) {{
case StateAcronym:
if (!isTerm || isLower || isDigit)
{
+ if (isLower && !config.GreedyAcronyms)
+ i -= 1;
CopyUtf8Term(input, ipos, output, ref opos, i - ipos, caseType, culture, /*termFilter,*/ true);
ipos = i;
state = isTerm ? StateWord : StateBreak;
@@ -780,9 +795,9 @@ function validateSafeAlias(id, value, immediate, callback) {{
if (isAcronym)
{
- if (caseType == CleanStringType.CamelCase && len <= 2 && opos > 0)
- caseType = CleanStringType.Unchanged;
- else if (caseType == CleanStringType.PascalCase && len <= 2)
+ if ((caseType == CleanStringType.CamelCase && len <= 2 && opos > 0) ||
+ (caseType == CleanStringType.PascalCase && len <= 2) ||
+ (caseType == CleanStringType.UmbracoCase))
caseType = CleanStringType.Unchanged;
}
@@ -822,6 +837,14 @@ function validateSafeAlias(id, value, immediate, callback) {{
opos += len - 1;
break;
+ case CleanStringType.UmbracoCase:
+ c = term[ipos++];
+ output[opos] = opos++ == 0 ? c : char.ToUpper(c, culture);
+ if (len > 1)
+ term.CopyTo(ipos, output, opos, len - 1);
+ opos += len - 1;
+ break;
+
default:
throw new ArgumentOutOfRangeException("caseType");
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 81276ea545..422c9c48b4 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -681,6 +681,7 @@
+
diff --git a/src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs
index b1be1cd6cb..fa53591297 100644
--- a/src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs
+++ b/src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs
@@ -64,19 +64,19 @@ namespace Umbraco.Tests.CoreStrings
#region Cases
[TestCase("foo", "foo")]
[TestCase(" foo ", "foo")]
- [TestCase("Foo", "foo")]
- [TestCase("FoO", "foO")]
- [TestCase("FoO bar", "foOBar")]
- [TestCase("FoO bar NIL", "foOBarNil")]
- [TestCase("FoO 33bar 22NIL", "foO33bar22Nil")]
- [TestCase("FoO 33bar 22NI", "foO33bar22NI")]
+ [TestCase("Foo", "Foo")]
+ [TestCase("FoO", "FoO")]
+ [TestCase("FoO bar", "FoOBar")]
+ [TestCase("FoO bar NIL", "FoOBarNIL")]
+ [TestCase("FoO 33bar 22NIL", "FoO33bar22NIL")]
+ [TestCase("FoO 33bar 22NI", "FoO33bar22NI")]
[TestCase("0foo", "foo")]
[TestCase("2foo bar", "fooBar")]
- [TestCase("9FOO", "foo")]
- [TestCase("foo-BAR", "fooBar")]
+ [TestCase("9FOO", "FOO")]
+ [TestCase("foo-BAR", "fooBAR")]
[TestCase("foo-BA-dang", "fooBADang")]
- [TestCase("foo_BAR", "fooBar")]
- [TestCase("foo'BAR", "fooBar")]
+ [TestCase("foo_BAR", "fooBAR")]
+ [TestCase("foo'BAR", "fooBAR")]
[TestCase("sauté dans l'espace", "sauteDansLespace")]
[TestCase("foo\"\"bar", "fooBar")]
[TestCase("-foo-", "foo")]
@@ -85,10 +85,14 @@ namespace Umbraco.Tests.CoreStrings
[TestCase("brô dëk ", "broDek")]
[TestCase("1235brô dëk ", "broDek")]
[TestCase("汉#字*/漢?字", "")]
- [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst")]
- [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst")]
- [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst")]
+ [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEFGXKLMNOPQrst")]
+ [TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEFGXKLMNOPQrst")]
+ [TestCase("AAA db cd EFG X KLMN OP qrst", "AAADbCdEFGXKLMNOPQrst")]
[TestCase("4 ways selector", "waysSelector")]
+ [TestCase("WhatIfWeDoItAgain", "WhatIfWeDoItAgain")]
+ [TestCase("whatIfWeDoItAgain", "whatIfWeDoItAgain")]
+ [TestCase("WhatIfWEDOITAgain", "WhatIfWEDOITAgain")]
+ [TestCase("WhatIfWe doItAgain", "WhatIfWeDoItAgain")]
#endregion
public void CleanStringForSafeAlias(string input, string expected)
{
@@ -244,8 +248,8 @@ namespace Umbraco.Tests.CoreStrings
#region Cases
[TestCase("1235brô dëK tzARlan ban123!pOo", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)]
[TestCase(" 1235brô dëK tzARlan ban123!pOo ", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)]
- [TestCase("1235brô dëK tzARlan ban123!pOo", "BroDeKTzARLanBan123POo", CleanStringType.PascalCase)]
- [TestCase("1235brô dëK tzARlan ban123!pOo", "broDeKTzARLanBan123POo", CleanStringType.CamelCase)]
+ [TestCase("1235brô dëK tzARlan ban123!pOo", "BroDeKTzARlanBan123POo", CleanStringType.PascalCase)]
+ [TestCase("1235brô dëK tzARlan ban123!pOo", "broDeKTzARlanBan123POo", CleanStringType.CamelCase)]
[TestCase("1235brô dëK tzARlan ban123!pOo", "BRODEKTZARLANBAN123POO", CleanStringType.UpperCase)]
[TestCase("1235brô dëK tzARlan ban123!pOo", "brodektzarlanban123poo", CleanStringType.LowerCase)]
[TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)]
@@ -277,10 +281,12 @@ namespace Umbraco.Tests.CoreStrings
[TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgaskeKe", CleanStringType.Unchanged)]
[TestCase("TRii", "TRii", CleanStringType.Unchanged)]
[TestCase("**TRii", "TRii", CleanStringType.Unchanged)]
- [TestCase("TRii", "trIi", CleanStringType.CamelCase)]
- [TestCase("**TRii", "trIi", CleanStringType.CamelCase)]
- [TestCase("TRii", "TRIi", CleanStringType.PascalCase)]
- [TestCase("**TRii", "TRIi", CleanStringType.PascalCase)]
+ [TestCase("TRii", "tRii", CleanStringType.CamelCase)]
+ [TestCase("TRXii", "trXii", CleanStringType.CamelCase)]
+ [TestCase("**TRii", "tRii", CleanStringType.CamelCase)]
+ [TestCase("TRii", "TRii", CleanStringType.PascalCase)]
+ [TestCase("TRXii", "TRXii", CleanStringType.PascalCase)]
+ [TestCase("**TRii", "TRii", CleanStringType.PascalCase)]
[TestCase("trII", "trII", CleanStringType.Unchanged)]
[TestCase("**trII", "trII", CleanStringType.Unchanged)]
[TestCase("trII", "trII", CleanStringType.CamelCase)]
@@ -299,14 +305,14 @@ namespace Umbraco.Tests.CoreStrings
}
#region Cases
- [TestCase("1235brô dëK tzARlan ban123!pOo", "bro de K tz AR lan ban123 p Oo", ' ', CleanStringType.Unchanged)]
- [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "bro de K tz AR lan ban123 p Oo", ' ', CleanStringType.Unchanged)]
- [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.PascalCase)]
- [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.PascalCase)]
- [TestCase("1235brô dëK tzARlan ban123!pOo", "bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.CamelCase)]
- [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-De-K-Tz-AR-Lan-Ban123-P-Oo", '-', CleanStringType.CamelCase)]
- [TestCase("1235brô dëK tzARlan ban123!pOo", "BRO-DE-K-TZ-AR-LAN-BAN123-P-OO", '-', CleanStringType.UpperCase)]
- [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-de-k-tz-ar-lan-ban123-p-oo", '-', CleanStringType.LowerCase)]
+ [TestCase("1235brô dëK tzARlan ban123!pOo", "bro de K tz A Rlan ban123 p Oo", ' ', CleanStringType.Unchanged)]
+ [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "bro de K tz A Rlan ban123 p Oo", ' ', CleanStringType.Unchanged)]
+ [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz A Rlan Ban123 P Oo", ' ', CleanStringType.PascalCase)]
+ [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz A Rlan Ban123 P Oo", ' ', CleanStringType.PascalCase)]
+ [TestCase("1235brô dëK tzARlan ban123!pOo", "bro De K Tz A Rlan Ban123 P Oo", ' ', CleanStringType.CamelCase)]
+ [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-De-K-Tz-A-Rlan-Ban123-P-Oo", '-', CleanStringType.CamelCase)]
+ [TestCase("1235brô dëK tzARlan ban123!pOo", "BRO-DE-K-TZ-A-RLAN-BAN123-P-OO", '-', CleanStringType.UpperCase)]
+ [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-de-k-tz-a-rlan-ban123-p-oo", '-', CleanStringType.LowerCase)]
[TestCase("Tab 1", "tab 1", ' ', CleanStringType.CamelCase)]
[TestCase("Home - Page", "home Page", ' ', CleanStringType.CamelCase)]
[TestCase("Shannon's Document Type", "shannon S Document Type", ' ', CleanStringType.CamelCase)]
diff --git a/src/Umbraco.Tests/CoreStrings/LegacyShortStringHelperTests.cs b/src/Umbraco.Tests/CoreStrings/LegacyShortStringHelperTests.cs
index 8df226e853..20a3ac0c86 100644
--- a/src/Umbraco.Tests/CoreStrings/LegacyShortStringHelperTests.cs
+++ b/src/Umbraco.Tests/CoreStrings/LegacyShortStringHelperTests.cs
@@ -46,6 +46,11 @@ namespace Umbraco.Tests.CoreStrings
[TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEFGXKLMNOPQrst")]
[TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEFGXKLMNOPQrst")]
[TestCase("AAA db cd EFG X KLMN OP qrst", "AAADbCdEFGXKLMNOPQrst")]
+ [TestCase("4 ways selector", "WaysSelector")]
+ [TestCase("WhatIfWeDoItAgain", "WhatIfWeDoItAgain")]
+ [TestCase("whatIfWeDoItAgain", "whatIfWeDoItAgain")]
+ [TestCase("WhatIfWEDOITAgain", "WhatIfWEDOITAgain")]
+ [TestCase("WhatIfWe doItAgain", "WhatIfWeDoItAgain")]
#endregion
public void CleanStringForSafeAlias(string input, string expected)
{
diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs
index 5698b36b1c..73e004faae 100644
--- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs
+++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs
@@ -14,7 +14,9 @@ namespace Umbraco.Web.Routing
/// by one specified template, using one specified Culture and RenderingEngine.
///
public class PublishedContentRequest
- {
+ {
+ private bool _readonly;
+
///
/// Triggers once the published content request has been prepared, but before it is processed.
///
@@ -74,6 +76,11 @@ namespace Umbraco.Web.Routing
{
if (Prepared != null)
Prepared(this, EventArgs.Empty);
+
+ if (!HasPublishedContent)
+ Is404 = true; // safety
+
+ _readonly = true;
}
///
@@ -82,6 +89,12 @@ namespace Umbraco.Web.Routing
/// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc.
public Uri Uri { get; private set; }
+ private void EnsureWriteable()
+ {
+ if (_readonly)
+ throw new InvalidOperationException("Cannot modify a PublishedContentRequest once it is read-only.");
+ }
+
#region PublishedContent
///
@@ -105,6 +118,7 @@ namespace Umbraco.Web.Routing
get { return _publishedContent; }
set
{
+ EnsureWriteable();
_publishedContent = value;
IsInternalRedirectPublishedContent = false;
TemplateModel = null;
@@ -119,6 +133,8 @@ namespace Umbraco.Web.Routing
/// preserve or reset the template, if any.
public void SetInternalRedirectPublishedContent(IPublishedContent content)
{
+ EnsureWriteable();
+
// unless a template has been set already by the finder,
// template should be null at that point.
var initial = IsInitialPublishedContent;
@@ -152,6 +168,8 @@ namespace Umbraco.Web.Routing
///
public void SetIsInitialPublishedContent()
{
+ EnsureWriteable();
+
// note: it can very well be null if the initial content was not found
_initialPublishedContent = _publishedContent;
IsInternalRedirectPublishedContent = false;
@@ -217,11 +235,13 @@ namespace Umbraco.Web.Routing
/// The alias of the template.
/// A value indicating whether a valid template with the specified alias was found.
///
- /// Successfully setting the template resets RenderingEngine to Unknown.
+ /// Successfully setting the template does refresh RenderingEngine.
/// If setting the template fails, then the previous template (if any) remains in place.
///
public bool TrySetTemplate(string alias)
{
+ EnsureWriteable();
+
if (string.IsNullOrWhiteSpace(alias))
{
TemplateModel = null;
@@ -239,6 +259,17 @@ namespace Umbraco.Web.Routing
return true;
}
+ ///
+ /// Sets the template to use to display the requested content.
+ ///
+ /// The template.
+ /// Setting the template does refresh RenderingEngine.
+ public void SetTemplate(ITemplate template)
+ {
+ EnsureWriteable();
+ TemplateModel = template;
+ }
+
///
/// Gets a value indicating whether the content request has a template.
///
@@ -270,10 +301,20 @@ namespace Umbraco.Web.Routing
get { return Domain != null; }
}
- ///
- /// Gets or sets the content request's culture.
- ///
- public CultureInfo Culture { get; set; }
+ private CultureInfo _culture;
+
+ ///
+ /// Gets or sets the content request's culture.
+ ///
+ public CultureInfo Culture
+ {
+ get { return _culture; }
+ set
+ {
+ EnsureWriteable();
+ _culture = value;
+ }
+ }
// note: do we want to have an ordered list of alternate cultures,
// to allow for fallbacks when doing dictionnary lookup and such?
@@ -332,6 +373,7 @@ namespace Umbraco.Web.Routing
/// where we want to allow developers to indicate a request is 404 but not to cancel it.
public void SetIs404()
{
+ EnsureWriteable();
Is404 = true;
}
@@ -358,6 +400,7 @@ namespace Umbraco.Web.Routing
/// redirect. Redirect will or will not take place in due time.
public void SetRedirect(string url)
{
+ EnsureWriteable();
RedirectUrl = url;
IsRedirectPermanent = false;
}
@@ -370,6 +413,7 @@ namespace Umbraco.Web.Routing
/// redirect. Redirect will or will not take place in due time.
public void SetRedirectPermanent(string url)
{
+ EnsureWriteable();
RedirectUrl = url;
IsRedirectPermanent = true;
}
@@ -383,6 +427,8 @@ namespace Umbraco.Web.Routing
/// redirect. Redirect will or will not take place in due time.
public void SetRedirect(string url, int status)
{
+ EnsureWriteable();
+
if (status < 300 || status > 308)
throw new ArgumentOutOfRangeException("status", "Valid redirection status codes 300-308.");
@@ -416,6 +462,8 @@ namespace Umbraco.Web.Routing
/// not be used, in due time.
public void SetResponseStatus(int code, string description = null)
{
+ EnsureWriteable();
+
// .Status is deprecated
// .SubStatusCode is IIS 7+ internal, ignore
ResponseStatusCode = code;
diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs
index 4f87a3532e..daeaa85cb8 100644
--- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs
+++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs
@@ -74,28 +74,22 @@ namespace Umbraco.Web.Routing
// trigger the Prepared event - at that point it is still possible to change about anything
// even though the request might be flagged for redirection - we'll redirect _after_ the event
+ //
+ // also, OnPrepared() will make the PublishedContentRequest readonly, so nothing can change
+ //
_pcr.OnPrepared();
- // we don't take care of anything xcept finding the rendering engine again
- // so if the content has changed, it's up to the user to find out the template
+ // we don't take care of anything so if the content has changed, it's up to the user
+ // to find out the appropriate template
// set the culture on the thread -- again, 'cos it might have changed in the event handler
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture;
- // if request has been flagged to redirect then return
- // whoever called us is in charge of actually redirecting
- if (_pcr.IsRedirect)
+ // if request has been flagged to redirect, or has no content to display,
+ // then return - whoever called us is in charge of actually redirecting
+ if (_pcr.IsRedirect || !_pcr.HasPublishedContent)
return;
- if (!_pcr.HasPublishedContent)
- {
- // safety
- _pcr.Is404 = true;
-
- // whoever called us is in charge of doing what's appropriate
- return;
- }
-
// we may be 404 _and_ have a content
// can't go beyond that point without a PublishedContent to render
diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs
index 1a5f344d9c..e800550949 100644
--- a/src/Umbraco.Web/Security/WebSecurity.cs
+++ b/src/Umbraco.Web/Security/WebSecurity.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
using System.Web;
using System.Web.Security;
using Umbraco.Core;
@@ -34,51 +33,53 @@ namespace Umbraco.Web.Security
IEnumerable allowGroups = null,
IEnumerable allowMembers = null)
{
+ if (allowAll)
+ return true;
+
if (allowTypes == null)
allowTypes = Enumerable.Empty();
if (allowGroups == null)
allowGroups = Enumerable.Empty();
if (allowMembers == null)
allowMembers = Enumerable.Empty();
-
+
// Allow by default
var allowAction = true;
-
- // If not set to allow all, need to check current loggined in member
- if (!allowAll)
+
+ // Get member details
+ var member = Member.GetCurrentMember();
+ if (member == null)
{
- // Get member details
- var member = Member.GetCurrentMember();
- if (member == null)
+ // If not logged on, not allowed
+ allowAction = false;
+ }
+ else
+ {
+ // If types defined, check member is of one of those types
+ var allowTypesList = allowTypes as IList ?? allowTypes.ToList();
+ if (allowTypesList.Any(allowType => allowType != string.Empty))
{
- // If not logged on, not allowed
- allowAction = false;
+ // Allow only if member's type is in list
+ allowAction = allowTypesList.Select(x => x.ToLowerInvariant()).Contains(member.ContentType.Alias.ToLowerInvariant());
}
- else
+
+ // If groups defined, check member is of one of those groups
+ var allowGroupsList = allowGroups as IList ?? allowGroups.ToList();
+ if (allowAction && allowGroupsList.Any(allowGroup => allowGroup != string.Empty))
{
- // If types defined, check member is of one of those types
- if (allowTypes.Any())
- {
- // Allow only if member's type is in list
- allowAction = allowTypes.Select(x => x.ToLowerInvariant()).Contains(member.ContentType.Alias.ToLowerInvariant());
- }
+ // Allow only if member's type is in list
+ var groups = Roles.GetRolesForUser(member.LoginName);
+ allowAction = groups.Select(s => s.ToLowerInvariant()).Intersect(groups.Select(myGroup => myGroup.ToLowerInvariant())).Any();
+ }
- // If groups defined, check member is of one of those groups
- if (allowAction && allowGroups.Any())
- {
- // Allow only if member's type is in list
- var groups = System.Web.Security.Roles.GetRolesForUser(member.LoginName);
- allowAction = groups.Select(s => s.ToLower()).Intersect(allowGroups).Any();
- }
-
- // If specific members defined, check member is of one of those
- if (allowAction && allowMembers.Any())
- {
- // Allow only if member's type is in list
- allowAction = allowMembers.Contains(member.Id);
- }
+ // If specific members defined, check member is of one of those
+ if (allowAction && allowMembers.Any())
+ {
+ // Allow only if member's type is in list
+ allowAction = allowMembers.Contains(member.Id);
}
}
+
return allowAction;
}
@@ -92,7 +93,7 @@ namespace Umbraco.Web.Security
}
private const long TicksPrMinute = 600000000;
- private static readonly int UmbracoTimeOutInMinutes = Core.Configuration.GlobalSettings.TimeOutInMinutes;
+ private static readonly int UmbracoTimeOutInMinutes = GlobalSettings.TimeOutInMinutes;
private User _currentUser;
@@ -122,7 +123,7 @@ namespace Umbraco.Web.Security
var retVal = Guid.NewGuid();
SqlHelper.ExecuteNonQuery(
"insert into umbracoUserLogins (contextID, userID, timeout) values (@contextId,'" + userId + "','" +
- (DateTime.Now.Ticks + (TicksPrMinute * UmbracoTimeOutInMinutes)).ToString() +
+ (DateTime.Now.Ticks + (TicksPrMinute * UmbracoTimeOutInMinutes)) +
"') ",
SqlHelper.CreateParameter("@contextId", retVal));
UmbracoUserContextId = retVal.ToString();
@@ -167,7 +168,8 @@ namespace Umbraco.Web.Security
///
internal bool ValidateBackOfficeCredentials(string username, string password)
{
- return Membership.Providers[UmbracoSettings.DefaultBackofficeProvider].ValidateUser(username, password);
+ var membershipProvider = Membership.Providers[UmbracoSettings.DefaultBackofficeProvider];
+ return membershipProvider != null && membershipProvider.ValidateUser(username, password);
}
///
@@ -180,7 +182,7 @@ namespace Umbraco.Web.Security
internal bool ValidateUserNodeTreePermissions(User umbracoUser, string path, string action)
{
var permissions = umbracoUser.GetPermissions(path);
- if (permissions.IndexOf(action) > -1 && (path.Contains("-20") || ("," + path + ",").Contains("," + umbracoUser.StartNodeId.ToString() + ",")))
+ if (permissions.IndexOf(action, StringComparison.Ordinal) > -1 && (path.Contains("-20") || ("," + path + ",").Contains("," + umbracoUser.StartNodeId + ",")))
return true;
var user = umbracoUser;
@@ -232,7 +234,7 @@ namespace Umbraco.Web.Security
SqlHelper.CreateParameter("@contextId", new Guid(UmbracoUserContextId))
);
}
-
+
return GetTimeout(UmbracoUserContextId);
}
@@ -244,21 +246,21 @@ namespace Umbraco.Web.Security
public int GetUserId(string umbracoUserContextId)
{
//need to parse to guid
- Guid gid;
- if (!Guid.TryParse(umbracoUserContextId, out gid))
+ Guid guid;
+ if (Guid.TryParse(umbracoUserContextId, out guid) == false)
{
return -1;
}
- var id = ApplicationContext.Current.ApplicationCache.GetCacheItem(
+ var id = ApplicationContext.Current.ApplicationCache.GetCacheItem(
CacheKeys.UserContextCacheKey + umbracoUserContextId,
- new TimeSpan(0, UmbracoTimeOutInMinutes/10, 0),
+ new TimeSpan(0, UmbracoTimeOutInMinutes / 10, 0),
() => SqlHelper.ExecuteScalar(
"select userID from umbracoUserLogins where contextID = @contextId",
- SqlHelper.CreateParameter("@contextId", gid)));
+ SqlHelper.CreateParameter("@contextId", guid)));
if (id == null)
return -1;
- return id.Value;
+ return id.Value;
}
///
@@ -301,7 +303,7 @@ namespace Umbraco.Web.Security
var user = User.GetUser(uid);
// Check for console access
- if (user.Disabled || (user.NoConsole && GlobalSettings.RequestIsInUmbracoApplication(httpContext) && !GlobalSettings.RequestIsLiveEditRedirector(httpContext)))
+ if (user.Disabled || (user.NoConsole && GlobalSettings.RequestIsInUmbracoApplication(httpContext) && GlobalSettings.RequestIsLiveEditRedirector(httpContext) == false))
{
if (throwExceptions) throw new ArgumentException("You have no priviledges to the umbraco console. Please contact your administrator");
return ValidateRequestAttempt.FailedNoPrivileges;
@@ -325,9 +327,9 @@ namespace Umbraco.Web.Security
internal ValidateRequestAttempt AuthorizeRequest(HttpContextBase httpContext, bool throwExceptions = false)
{
// check for secure connection
- if (GlobalSettings.UseSSL && !httpContext.Request.IsSecureConnection)
+ if (GlobalSettings.UseSSL && httpContext.Request.IsSecureConnection == false)
{
- if (throwExceptions) throw new UserAuthorizationException("This installation requires a secure connection (via SSL). Please update the URL to include https://");
+ if (throwExceptions) throw new UserAuthorizationException("This installation requires a secure connection (via SSL). Please update the URL to include https://");
return ValidateRequestAttempt.FailedNoSsl;
}
return ValidateCurrentUser(httpContext, throwExceptions);
@@ -377,10 +379,14 @@ namespace Umbraco.Web.Security
try
{
var encTicket = StateHelper.Cookies.UserContext.GetValue();
- if (!string.IsNullOrEmpty(encTicket))
- return FormsAuthentication.Decrypt(encTicket).UserData;
+ if (string.IsNullOrEmpty(encTicket) == false)
+ {
+ var formsAuthenticationTicket = FormsAuthentication.Decrypt(encTicket);
+ if (formsAuthenticationTicket != null)
+ return formsAuthenticationTicket.UserData;
+ }
}
- catch (HttpException ex)
+ catch (HttpException)
{
// we swallow this type of exception as it happens if a legacy (pre 4.8.1) cookie is set
}
@@ -400,7 +406,7 @@ namespace Umbraco.Web.Security
if (StateHelper.Cookies.UserContext.HasValue)
StateHelper.Cookies.ClearAll();
- if (!String.IsNullOrEmpty(value))
+ if (string.IsNullOrEmpty(value) == false)
{
var ticket = new FormsAuthenticationTicket(1,
value,
@@ -411,13 +417,10 @@ namespace Umbraco.Web.Security
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
- var encTicket = FormsAuthentication.Encrypt(ticket);
-
-
+ FormsAuthentication.Encrypt(ticket);
+
// Create new cookie.
StateHelper.Cookies.UserContext.SetValue(value, 1);
-
-
}
else
{
diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs
index b44026febe..471e7403ad 100644
--- a/src/Umbraco.Web/UmbracoContext.cs
+++ b/src/Umbraco.Web/UmbracoContext.cs
@@ -274,7 +274,7 @@ namespace Umbraco.Web
///
/// Gets/sets the PublishedContentRequest object
///
- internal PublishedContentRequest PublishedContentRequest { get; set; }
+ public PublishedContentRequest PublishedContentRequest { get; set; }
///
/// Exposes the HttpContext for the current request