From d7081dbc6bba5c648f0eff7e0d804525ce03da98 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Thu, 11 Apr 2013 17:12:14 -0200 Subject: [PATCH 1/6] Implementing new RelationService. Making EntityService public as it will be used within the RelationService. --- src/Umbraco.Core/Services/EntityService.cs | 2 +- src/Umbraco.Core/Services/RelationService.cs | 407 +++++++++++++++++++ src/Umbraco.Core/Services/ServiceContext.cs | 14 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 4 files changed, 422 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Core/Services/RelationService.cs 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/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 @@ + From e3af8b4e5f7ae0497edb1a1fe2638a8a95d2a943 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 11 Apr 2013 11:44:15 -0200 Subject: [PATCH 2/6] Web.Routing - public, though readonly, UmbracoContext.PublishedContentRequest --- .../Routing/PublishedContentRequest.cs | 47 +++++++++++++++++-- .../Routing/PublishedContentRequestEngine.cs | 22 ++++----- src/Umbraco.Web/UmbracoContext.cs | 2 +- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index 5698b36b1c..b3c5ac3db9 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; @@ -222,6 +240,8 @@ namespace Umbraco.Web.Routing /// public bool TrySetTemplate(string alias) { + EnsureWriteable(); + if (string.IsNullOrWhiteSpace(alias)) { TemplateModel = null; @@ -270,10 +290,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 +362,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 +389,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 +402,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 +416,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 +451,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/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 From 5154bcd2e9b8c1da3ba52e6ba1f3126f62a095fb Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 11 Apr 2013 11:55:32 -0200 Subject: [PATCH 3/6] Web.Routing - template setter on PublishedContentRequest --- src/Umbraco.Web/Routing/PublishedContentRequest.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index b3c5ac3db9..73e004faae 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs @@ -235,7 +235,7 @@ 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) @@ -259,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. /// From 0b3eaf9c771a25dcc77381d13823228b24bd7d18 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 12 Apr 2013 05:33:39 -0200 Subject: [PATCH 4/6] Core.Strings - refactor DefaultShortStringHelper for better backward compat. --- src/Umbraco.Core/Strings/CleanStringType.cs | 9 ++- .../Strings/DefaultShortStringHelper.cs | 33 ++++++++-- .../DefaultShortStringHelperTests.cs | 60 ++++++++++--------- .../LegacyShortStringHelperTests.cs | 5 ++ 4 files changed, 74 insertions(+), 33 deletions(-) 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..306d86c85d 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.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) { From 43d6c9a46dd50f5ff0fd0c1ab20700a271aab898 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 12 Apr 2013 11:32:37 -0200 Subject: [PATCH 5/6] Core.Strings - bugfix DefaultShortStringHelper --- src/Umbraco.Core/Strings/DefaultShortStringHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index 306d86c85d..880c5d110f 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -733,7 +733,7 @@ function validateSafeAlias(id, value, immediate, callback) {{ case StateAcronym: if (!isTerm || isLower || isDigit) { - if (isLower || !config.GreedyAcronyms) + if (isLower && !config.GreedyAcronyms) i -= 1; CopyUtf8Term(input, ipos, output, ref opos, i - ipos, caseType, culture, /*termFilter,*/ true); ipos = i; From a06951afbd2368b5bdd280ab1c601c60800e0679 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 15 Apr 2013 12:18:40 -0200 Subject: [PATCH 6/6] Fix MemberAuthorizeAttribute for UmbracoApiControllers --- src/Umbraco.Web/Security/WebSecurity.cs | 111 ++++++++++++------------ 1 file changed, 57 insertions(+), 54 deletions(-) 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 {