Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 838699d884 | |||
| ac1ce4346e | |||
| 93d3a92d25 | |||
| 00e1ecb80e | |||
| ab640455b1 | |||
| 4c1e5072dd | |||
| baf703646d | |||
| ebb3f580aa | |||
| 24b855a207 | |||
| 1b77c39507 | |||
| 3e76623887 | |||
| e667cdbc66 | |||
| c364610f1d | |||
| c4d07dbcc5 | |||
| 77644c7bee | |||
| 6ddaa7cd4d | |||
| 14d4bbd854 | |||
| 30df91bab9 | |||
| 2490355934 | |||
| fdc5976035 | |||
| e1702cec60 | |||
| 9d220c549b | |||
| 51250d40a8 | |||
| 9677b01f54 | |||
| 847d6f3823 | |||
| c4faa084a1 | |||
| ec573f0b46 | |||
| 333f337ffc | |||
| 4472623595 | |||
| df34723a1e | |||
| c284c0656e | |||
| 107958cccb | |||
| 3a4d05fe79 | |||
| b7e126c577 | |||
| 5a91e20b82 | |||
| 8cfa707dd4 | |||
| 643a2850fa | |||
| 600820e14e | |||
| c866ecbe7b | |||
| 3fb0e65d90 | |||
| 9e09443380 | |||
| 1624919f4a | |||
| 2b502e89f7 | |||
| 45aea76a11 | |||
| 13c6b6ed7c | |||
| 8a0edee12e | |||
| 772cffbe11 | |||
| eef4dfc909 | |||
| e04d1dd5d1 | |||
| 0e01b04fff | |||
| 41cdfecd12 | |||
| b61aec3156 | |||
| 15b5dcfd56 | |||
| 26b95afdad | |||
| fa002ec3b2 | |||
| 2e777121d6 | |||
| 5d40f85116 | |||
| 311d0ae85d | |||
| 2559be86e5 | |||
| c2958471e7 | |||
| bb6bde247b | |||
| f0c1d3dfe2 | |||
| f9bb36c95c | |||
| 7da271ec40 | |||
| 6fcc59684b | |||
| 99b7f66c41 | |||
| 1e01d8272b | |||
| c3fe82a99d | |||
| c2b96c7211 | |||
| 0a24581836 |
@@ -100,6 +100,16 @@ namespace Umbraco.Core
|
||||
public static class Member
|
||||
{
|
||||
/// <summary>
|
||||
/// The label for the field to store the segment data for the member
|
||||
/// </summary>
|
||||
public const string SegmentsLabel = "Segments";
|
||||
|
||||
/// <summary>
|
||||
/// The property type alias for the field to store the segment data for the member
|
||||
/// </summary>
|
||||
public const string Segments = "umbracoMemberSegments";
|
||||
|
||||
/// <summary>
|
||||
/// if a role starts with __umbracoRole we won't show it as it's an internal role used for public access
|
||||
/// </summary>
|
||||
public static readonly string InternalRolePrefix = "__umbracoRole";
|
||||
@@ -251,6 +261,14 @@ namespace Umbraco.Core
|
||||
Alias = PasswordQuestion,
|
||||
Name = PasswordQuestionLabel
|
||||
}
|
||||
},
|
||||
{
|
||||
Segments,
|
||||
new PropertyType(PropertyEditors.NoEditAlias, DataTypeDatabaseType.Ntext, true)
|
||||
{
|
||||
Alias = Segments,
|
||||
Name = SegmentsLabel
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
/// </summary>
|
||||
public const string AuthCookieName = "UMB_UCONTEXT";
|
||||
|
||||
/// <summary>
|
||||
/// The segment cookie name
|
||||
/// </summary>
|
||||
public const string SegmentCookieName = "UMB_SEGMENTS";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
@@ -21,6 +22,27 @@ namespace Umbraco.Core.Models
|
||||
private int _writer;
|
||||
private string _nodeName;//NOTE Once localization is introduced this will be the non-localized Node Name.
|
||||
private bool _permissionsChanged;
|
||||
|
||||
public Content(string name, int parentId, IContentType contentType, PropertyCollection properties, VariantInfo variantInfo)
|
||||
: base(name, parentId, contentType, properties)
|
||||
{
|
||||
Mandate.ParameterNotNull(variantInfo, "variantInfo");
|
||||
Mandate.ParameterNotNull(contentType, "contentType");
|
||||
|
||||
_contentType = contentType;
|
||||
VariantInfo = variantInfo;
|
||||
}
|
||||
|
||||
public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, VariantInfo variantInfo)
|
||||
: base(name, parent, contentType, properties)
|
||||
{
|
||||
Mandate.ParameterNotNull(contentType, "contentType");
|
||||
Mandate.ParameterNotNull(variantInfo, "variantInfo");
|
||||
|
||||
_contentType = contentType;
|
||||
VariantInfo = variantInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating a Content object
|
||||
/// </summary>
|
||||
@@ -40,11 +62,8 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="contentType">ContentType for the current Content object</param>
|
||||
/// <param name="properties">Collection of properties</param>
|
||||
public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties)
|
||||
: base(name, parent, contentType, properties)
|
||||
{
|
||||
Mandate.ParameterNotNull(contentType, "contentType");
|
||||
|
||||
_contentType = contentType;
|
||||
: this(name, parent, contentType, properties, new VariantInfo())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -65,12 +84,9 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="parentId">Id of the Parent content</param>
|
||||
/// <param name="contentType">ContentType for the current Content object</param>
|
||||
/// <param name="properties">Collection of properties</param>
|
||||
public Content(string name, int parentId, IContentType contentType, PropertyCollection properties)
|
||||
: base(name, parentId, contentType, properties)
|
||||
{
|
||||
Mandate.ParameterNotNull(contentType, "contentType");
|
||||
|
||||
_contentType = contentType;
|
||||
public Content(string name, int parentId, IContentType contentType, PropertyCollection properties)
|
||||
: this(name, parentId, contentType, properties, new VariantInfo())
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo<Content, ITemplate>(x => x.Template);
|
||||
@@ -82,6 +98,11 @@ namespace Umbraco.Core.Models
|
||||
private static readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo<Content, string>(x => x.NodeName);
|
||||
private static readonly PropertyInfo PermissionsChangedSelector = ExpressionHelper.GetPropertyInfo<Content, bool>(x => x.PermissionsChanged);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the variant definition for this content item
|
||||
/// </summary>
|
||||
public VariantInfo VariantInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the template used by the Content.
|
||||
/// This is used to override the default one from the ContentType.
|
||||
@@ -454,6 +475,8 @@ namespace Umbraco.Core.Models
|
||||
clone._contentType = (IContentType)ContentType.DeepClone();
|
||||
clone.ResetDirtyProperties(false);
|
||||
|
||||
clone.VariantInfo = (VariantInfo)VariantInfo.DeepClone();
|
||||
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Models.ContentVariations
|
||||
{
|
||||
/// <summary>
|
||||
/// A class representing the variant info, whether it is a variant itself or a master doc
|
||||
/// </summary>
|
||||
public class VariantInfo
|
||||
{
|
||||
public VariantInfo()
|
||||
{
|
||||
IsVariant = false;
|
||||
VariantIds = new int[] {};
|
||||
}
|
||||
|
||||
public VariantInfo(params int[] variantIds)
|
||||
{
|
||||
VariantIds = variantIds.Any()
|
||||
? variantIds
|
||||
: new int[] {};
|
||||
|
||||
IsVariant = false;
|
||||
}
|
||||
|
||||
public VariantInfo(int masterDocId, string key)
|
||||
{
|
||||
IsVariant = true;
|
||||
MasterDocId = masterDocId;
|
||||
Key = key;
|
||||
VariantIds = new int[] { };
|
||||
}
|
||||
|
||||
public int[] VariantIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this is a variant (i.e. not a master doc)
|
||||
/// </summary>
|
||||
public bool IsVariant { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The master doc id if this is a variant
|
||||
/// </summary>
|
||||
public int MasterDocId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// They key stored with this variant when it is a variant.
|
||||
/// </summary>
|
||||
public string Key { get; private set; }
|
||||
|
||||
///// <summary>
|
||||
///// The child variants for this node - if it is a master doc
|
||||
///// </summary>
|
||||
//public IEnumerable<ChildVariant> ChildVariants { get; private set; }
|
||||
|
||||
public object DeepClone()
|
||||
{
|
||||
var clone = (VariantInfo)MemberwiseClone();
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
@@ -9,6 +10,11 @@ namespace Umbraco.Core.Models
|
||||
/// </summary>
|
||||
public interface IContent : IContentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the variant definition for this node
|
||||
/// </summary>
|
||||
VariantInfo VariantInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the template used by the Content.
|
||||
/// This is used to override the default one from the ContentType.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Factories
|
||||
@@ -10,13 +11,15 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
private readonly IContentType _contentType;
|
||||
private readonly Guid _nodeObjectTypeId;
|
||||
private readonly int _id;
|
||||
private readonly VariantInfo _variantInfo;
|
||||
private int _primaryKey;
|
||||
|
||||
public ContentFactory(IContentType contentType, Guid nodeObjectTypeId, int id)
|
||||
public ContentFactory(IContentType contentType, Guid nodeObjectTypeId, int id, VariantInfo variantInfo)
|
||||
{
|
||||
_contentType = contentType;
|
||||
_nodeObjectTypeId = nodeObjectTypeId;
|
||||
_id = id;
|
||||
_variantInfo = variantInfo;
|
||||
}
|
||||
|
||||
public ContentFactory(Guid nodeObjectTypeId, int id)
|
||||
@@ -29,7 +32,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
public IContent BuildEntity(DocumentDto dto)
|
||||
{
|
||||
var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType)
|
||||
var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType, new PropertyCollection(), _variantInfo)
|
||||
{
|
||||
Id = _id,
|
||||
Key =
|
||||
|
||||
@@ -3,10 +3,12 @@ using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
@@ -26,7 +28,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
private readonly IContentTypeRepository _contentTypeRepository;
|
||||
private readonly ITemplateRepository _templateRepository;
|
||||
private readonly ITagsRepository _tagRepository;
|
||||
private readonly ITagsRepository _tagRepository;
|
||||
private readonly CacheHelper _cacheHelper;
|
||||
private readonly ContentPreviewRepository<IContent> _contentPreviewRepository;
|
||||
private readonly ContentXmlRepository<IContent> _contentXmlRepository;
|
||||
@@ -40,6 +42,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
_contentTypeRepository = contentTypeRepository;
|
||||
_templateRepository = templateRepository;
|
||||
_tagRepository = tagRepository;
|
||||
|
||||
_cacheHelper = cacheHelper;
|
||||
_contentPreviewRepository = new ContentPreviewRepository<IContent>(work, NullCacheProvider.Current);
|
||||
_contentXmlRepository = new ContentXmlRepository<IContent>(work, NullCacheProvider.Current);
|
||||
@@ -56,6 +59,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
_contentTypeRepository = contentTypeRepository;
|
||||
_templateRepository = templateRepository;
|
||||
_tagRepository = tagRepository;
|
||||
|
||||
_cacheHelper = cacheHelper;
|
||||
_contentPreviewRepository = new ContentPreviewRepository<IContent>(work, NullCacheProvider.Current);
|
||||
_contentXmlRepository = new ContentXmlRepository<IContent>(work, NullCacheProvider.Current);
|
||||
@@ -173,7 +177,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
get { return new Guid(Constants.ObjectTypes.Document); }
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of VersionableRepositoryBase<IContent>
|
||||
@@ -253,8 +257,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
((Content)entity).AddingEntity();
|
||||
|
||||
//Ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
|
||||
//Ensure unique name on the same level when it is NOT a variant
|
||||
if (entity.VariantInfo.IsVariant == false)
|
||||
{
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
|
||||
}
|
||||
|
||||
|
||||
var factory = new ContentFactory(NodeObjectTypeId, entity.Id);
|
||||
var dto = factory.BuildDto(entity);
|
||||
@@ -338,7 +346,41 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
property.Id = keyDictionary[property.PropertyTypeId];
|
||||
}
|
||||
|
||||
//lastly, check if we are a creating a published version , then update the tags table
|
||||
//Check if this is a content variant, if it is then we need to create a relation accordingly
|
||||
if (entity.VariantInfo.IsVariant)
|
||||
{
|
||||
var relType = Database.FirstOrDefault<RelationTypeDto>(
|
||||
new Sql().Select("*").From<RelationTypeDto>().Where<RelationTypeDto>(typeDto => typeDto.Alias == "umbContentVariants"));
|
||||
object relId;
|
||||
if (relType == null)
|
||||
{
|
||||
relId = Database.Insert(new RelationTypeDto
|
||||
{
|
||||
Alias = "umbContentVariants",
|
||||
ChildObjectType = new Guid(Constants.ObjectTypes.Document),
|
||||
ParentObjectType = new Guid(Constants.ObjectTypes.Document),
|
||||
Name = "umbContentVariants"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
relId = relType.Id;
|
||||
}
|
||||
|
||||
var asInt = relId.TryConvertTo<int>();
|
||||
|
||||
Database.Insert(new RelationDto
|
||||
{
|
||||
ChildId = entity.Id,
|
||||
ParentId = entity.VariantInfo.MasterDocId,
|
||||
Datetime = DateTime.Now,
|
||||
RelationType = asInt.Result,
|
||||
//the comment is the variant key
|
||||
Comment = entity.VariantInfo.Key
|
||||
});
|
||||
}
|
||||
|
||||
//check if we are a creating a published version , then update the tags table
|
||||
if (entity.Published)
|
||||
{
|
||||
UpdatePropertyTags(entity, _tagRepository);
|
||||
@@ -363,8 +405,19 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
entity.UpdateDate = DateTime.Now;
|
||||
}
|
||||
|
||||
//Ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
|
||||
var factory = new ContentFactory(NodeObjectTypeId, entity.Id);
|
||||
|
||||
if (entity.VariantInfo.IsVariant)
|
||||
{
|
||||
//If it is a variant, ensure it always has the same name as it's master
|
||||
var master = Get(entity.VariantInfo.MasterDocId);
|
||||
entity.Name = master.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Ensure unique name on the same level when it is NOT a variant
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
|
||||
}
|
||||
|
||||
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
|
||||
if (((ICanBeDirty)entity).IsPropertyDirty("ParentId"))
|
||||
@@ -383,7 +436,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
// Gonna just leave it as is for now, and not re-propogate permissions.
|
||||
}
|
||||
|
||||
var factory = new ContentFactory(NodeObjectTypeId, entity.Id);
|
||||
|
||||
//Look up Content entry to get Primary for updating the DTO
|
||||
var contentDto = Database.SingleOrDefault<ContentDto>("WHERE nodeId = @Id", new { Id = entity.Id });
|
||||
factory.SetPrimaryKey(contentDto.PrimaryKey);
|
||||
@@ -479,8 +532,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
property.Id = keyDictionary[property.PropertyTypeId];
|
||||
}
|
||||
}
|
||||
|
||||
//lastly, check if we are a newly published version and then update the tags table
|
||||
|
||||
//check if we are a newly published version and then update the tags table
|
||||
if (isNewPublishedVersion)
|
||||
{
|
||||
UpdatePropertyTags(entity, _tagRepository);
|
||||
@@ -558,6 +611,40 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
internal VariantInfo GetVariantInfo(int contentId)
|
||||
{
|
||||
var sql = new Sql()
|
||||
.Select("umbracoRelation.*")
|
||||
.From<RelationDto>()
|
||||
.InnerJoin<RelationTypeDto>()
|
||||
.On<RelationDto, RelationTypeDto>(dto => dto.RelationType, dto => dto.Id)
|
||||
.Where("umbracoRelationType.alias = @alias AND (umbracoRelation.parentId = @parentId OR umbracoRelation.childId = @childId)",
|
||||
new {parentId = contentId, childId = contentId, alias = "umbContentVariants"});
|
||||
|
||||
var result = Database.Fetch<dynamic>(sql);
|
||||
|
||||
//first check if the result has the current content id as a childid in the collection,
|
||||
// if it does, then it means that this content item is a variant, otherwise if it's id is
|
||||
// contained in any parent ids then it's a master
|
||||
var variant = result.Where(x => x.childId == contentId).ToArray();
|
||||
if (variant.Any())
|
||||
{
|
||||
var row = variant.Single();
|
||||
//this content item is a variant itself
|
||||
return new VariantInfo(
|
||||
//get it's master doc id
|
||||
row.parentId,
|
||||
row.comment);
|
||||
}
|
||||
|
||||
var childVariants = result.Where(x => x.parentId == contentId)
|
||||
.Select(x => (int)x.childId)
|
||||
.ToArray();
|
||||
|
||||
//it's a master doc
|
||||
return new VariantInfo(childVariants);
|
||||
}
|
||||
|
||||
public IContent GetByLanguage(int id, string language)
|
||||
{
|
||||
var sql = GetBaseQuery(false);
|
||||
@@ -638,8 +725,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
|
||||
|
||||
var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId);
|
||||
var content = factory.BuildEntity(dto);
|
||||
var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId, GetVariantInfo(dto.NodeId));
|
||||
var content = (Content)factory.BuildEntity(dto);
|
||||
|
||||
//Check if template id is set on DocumentDto, and get ITemplate if it is.
|
||||
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
|
||||
@@ -648,7 +735,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
|
||||
content.Properties = GetPropertyCollection(dto.NodeId, versionId, contentType, content.CreateDate, content.UpdateDate);
|
||||
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
((Entity)content).ResetDirtyProperties(false);
|
||||
@@ -660,19 +747,34 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
if (EnsureUniqueNaming == false)
|
||||
return nodeName;
|
||||
|
||||
var sql = new Sql();
|
||||
sql.Select("*")
|
||||
var docSql = new Sql()
|
||||
.Select("*")
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName));
|
||||
|
||||
//get all variants ids for this level so we can ignore them in the comparison
|
||||
var childVarSql = new Sql()
|
||||
.Select("umbracoNode.id")
|
||||
.From<NodeDto>()
|
||||
.InnerJoin<RelationDto>()
|
||||
.On<RelationDto, NodeDto>(dto => dto.ChildId, dto => dto.NodeId)
|
||||
.InnerJoin<RelationTypeDto>()
|
||||
.On<RelationTypeDto, RelationDto>(dto => dto.Id, dto => dto.RelationType)
|
||||
.Where<RelationTypeDto>(x => x.Alias == "umbContentVariants")
|
||||
.Where<NodeDto>(x => x.ParentId == parentId);
|
||||
|
||||
int uniqueNumber = 1;
|
||||
var currentName = nodeName;
|
||||
|
||||
var variantIds = Database.Fetch<int>(childVarSql);
|
||||
|
||||
var dtos = Database.Fetch<NodeDto>(docSql)
|
||||
.Where(x => variantIds.Contains(x.NodeId) == false)
|
||||
.OrderBy(x => x.Text, new SimilarNodeNameComparer());
|
||||
|
||||
var dtos = Database.Fetch<NodeDto>(sql);
|
||||
if (dtos.Any())
|
||||
{
|
||||
var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer());
|
||||
foreach (var dto in results)
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
if(id != 0 && id == dto.NodeId) continue;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
|
||||
@@ -9,6 +10,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IContentRepository : IRepositoryVersionable<int, IContent>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific language version of an <see cref="IContent"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using Umbraco.Core.Models;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IRelationRepository : IRepositoryQueryable<int, IRelation>
|
||||
{
|
||||
|
||||
IEnumerable<IRelation> GetByParentId(int id, string relationTypeAlias);
|
||||
IEnumerable<IRelation> GetByParentIds(int[] ids, string relationTypeAlias);
|
||||
IEnumerable<IRelation> GetByChildIds(int[] ids, string relationTypeAlias);
|
||||
IEnumerable<IRelation> GetByParentOrChildId(int id, string relationTypeAlias);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,6 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IRelationTypeRepository : IRepositoryQueryable<int, IRelationType>
|
||||
{
|
||||
|
||||
IRelationType GetByAlias(string relationTypeAlias);
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
var factory = new RelationFactory(relationType);
|
||||
var entity = factory.BuildEntity(dto);
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
((TracksChangesEntityBase)entity).ResetDirtyProperties(false);
|
||||
|
||||
@@ -57,6 +56,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
protected override IEnumerable<IRelation> PerformGetAll(params int[] ids)
|
||||
{
|
||||
//TODO: Performance here is no good, we can easily do a query with an SQL 'IN' operator
|
||||
// to acheive this!!!
|
||||
|
||||
if (ids.Any())
|
||||
{
|
||||
foreach (var id in ids)
|
||||
@@ -82,6 +84,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
var dtos = Database.Fetch<RelationDto>(sql);
|
||||
|
||||
//TODO: This is gonna be pretty horrible for performance, should really just convert the already fetched list.
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
yield return Get(dto.Id);
|
||||
@@ -148,5 +151,97 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public IEnumerable<IRelation> GetByParentId(int id, string relationTypeAlias)
|
||||
{
|
||||
//TODO: Caching?
|
||||
|
||||
var relationType = _relationTypeRepository.GetByAlias(relationTypeAlias);
|
||||
if (relationType == null)
|
||||
{
|
||||
return Enumerable.Empty<IRelation>();
|
||||
}
|
||||
|
||||
var sql = new Sql();
|
||||
sql.Select("umbracoRelation.*")
|
||||
.From<RelationDto>()
|
||||
.InnerJoin<RelationTypeDto>()
|
||||
.On<RelationDto, RelationTypeDto>(dto => dto.RelationType, dto => dto.Id)
|
||||
.Where<RelationDto>(dto => dto.ParentId == id)
|
||||
.Where<RelationTypeDto>(dto => dto.Alias == relationTypeAlias);
|
||||
|
||||
var factory = new RelationFactory(relationType);
|
||||
|
||||
return Database.Fetch<RelationDto>(sql).Select(factory.BuildEntity).ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<IRelation> GetByParentIds(int[] ids, string relationTypeAlias)
|
||||
{
|
||||
//TODO: Caching?
|
||||
|
||||
var relationType = _relationTypeRepository.GetByAlias(relationTypeAlias);
|
||||
if (relationType == null)
|
||||
{
|
||||
return Enumerable.Empty<IRelation>();
|
||||
}
|
||||
|
||||
var sql = new Sql();
|
||||
sql.Select("umbracoRelation.*")
|
||||
.From<RelationDto>()
|
||||
.InnerJoin<RelationTypeDto>()
|
||||
.On<RelationDto, RelationTypeDto>(dto => dto.RelationType, dto => dto.Id)
|
||||
.Where("umbracoRelation.parentId IN (@ids)", new {ids = ids})
|
||||
.Where<RelationTypeDto>(dto => dto.Alias == relationTypeAlias);
|
||||
|
||||
var factory = new RelationFactory(relationType);
|
||||
|
||||
return Database.Fetch<RelationDto>(sql).Select(factory.BuildEntity).ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<IRelation> GetByChildIds(int[] ids, string relationTypeAlias)
|
||||
{
|
||||
//TODO: Caching?
|
||||
|
||||
var relationType = _relationTypeRepository.GetByAlias(relationTypeAlias);
|
||||
if (relationType == null)
|
||||
{
|
||||
return Enumerable.Empty<IRelation>();
|
||||
}
|
||||
|
||||
var sql = new Sql();
|
||||
sql.Select("umbracoRelation.*")
|
||||
.From<RelationDto>()
|
||||
.InnerJoin<RelationTypeDto>()
|
||||
.On<RelationDto, RelationTypeDto>(dto => dto.RelationType, dto => dto.Id)
|
||||
.Where("umbracoRelation.childId IN (@ids)", new { ids = ids })
|
||||
.Where<RelationTypeDto>(dto => dto.Alias == relationTypeAlias);
|
||||
|
||||
var factory = new RelationFactory(relationType);
|
||||
|
||||
return Database.Fetch<RelationDto>(sql).Select(factory.BuildEntity).ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<IRelation> GetByParentOrChildId(int id, string relationTypeAlias)
|
||||
{
|
||||
//TODO: Caching?
|
||||
|
||||
var relationType = _relationTypeRepository.GetByAlias(relationTypeAlias);
|
||||
if (relationType == null)
|
||||
{
|
||||
return Enumerable.Empty<IRelation>();
|
||||
}
|
||||
|
||||
var sql = new Sql();
|
||||
sql.Select("umbracoRelation.*")
|
||||
.From<RelationDto>()
|
||||
.InnerJoin<RelationTypeDto>()
|
||||
.On<RelationDto, RelationTypeDto>(dto => dto.RelationType, dto => dto.Id)
|
||||
.Where<RelationDto>(dto => dto.ParentId == id || dto.ChildId == id)
|
||||
.Where<RelationTypeDto>(dto => dto.Alias == relationTypeAlias);
|
||||
|
||||
var factory = new RelationFactory(relationType);
|
||||
|
||||
return Database.Fetch<RelationDto>(sql).Select(factory.BuildEntity).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,5 +141,22 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public IRelationType GetByAlias(string relationTypeAlias)
|
||||
{
|
||||
var sql = new Sql();
|
||||
sql.Select("*")
|
||||
.From<RelationTypeDto>()
|
||||
.Where<RelationTypeDto>(d => d.Alias == relationTypeAlias);
|
||||
|
||||
var factory = new RelationTypeFactory();
|
||||
var dto = Database.FirstOrDefault<RelationTypeDto>(sql);
|
||||
|
||||
//TODO: Caching?
|
||||
|
||||
return dto == null
|
||||
? null
|
||||
: factory.BuildEntity(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,6 +226,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
try
|
||||
{
|
||||
PersistNewItem((TEntity)entity);
|
||||
|
||||
//TODO: Currently the repositories are handling a caching layer BUT if this transaction fails
|
||||
// the repositories are still clearing their cache whereas their cache should only be refreshed
|
||||
// after the transaction is completed - need to re-visit how caching is working in repo's
|
||||
// This executes BEFORE the transaction is complete!
|
||||
|
||||
_cache.Save(typeof(TEntity), entity);
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -247,6 +253,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
try
|
||||
{
|
||||
PersistUpdatedItem((TEntity)entity);
|
||||
|
||||
//TODO: Currently the repositories are handling a caching layer BUT if this transaction fails
|
||||
// the repositories are still clearing their cache whereas their cache should only be refreshed
|
||||
// after the transaction is completed - need to re-visit how caching is working in repo's
|
||||
// This executes BEFORE the transaction is complete!
|
||||
|
||||
_cache.Save(typeof(TEntity), entity);
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -266,6 +278,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public virtual void PersistDeletedItem(IEntity entity)
|
||||
{
|
||||
PersistDeletedItem((TEntity)entity);
|
||||
|
||||
//TODO: Currently the repositories are handling a caching layer BUT if this transaction fails
|
||||
// the repositories are still clearing their cache whereas their cache should only be refreshed
|
||||
// after the transaction is completed - need to re-visit how caching is working in repo's
|
||||
// This executes BEFORE the transaction is complete!
|
||||
|
||||
_cache.Delete(typeof(TEntity), entity);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,17 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
/// </param>
|
||||
internal void Commit(Action<UmbracoDatabase> transactionCompleting)
|
||||
{
|
||||
//TODO: Currently the repositories are handling a caching layer BUT if this transaction fails
|
||||
// the repositories are still clearing their cache whereas their cache should only be refreshed
|
||||
// after the transaction is completed - need to re-visit how caching is working in repo's
|
||||
//One way to acheive this could be to return some instructions/results from the "Persist..." methods
|
||||
// then pass these results back to the repository after the transaction is complete and they can
|
||||
// take care of cache refreshing there. The benefit of this is that these operations could return multiple
|
||||
// items to cache refresh since any given operation could affect multiple items, NOT just one!
|
||||
//In the meantime we have to do the regular cache refresher thing but that means having to re-lookup the variants for each
|
||||
// content item and then clear their cache. It might just be possible to simplify cache refreshers by having the
|
||||
// repositories return a result of all entities that have been modified and just use that to refresh the cache.
|
||||
|
||||
using (var transaction = Database.GetTransaction())
|
||||
{
|
||||
foreach (var operation in _operations.OrderBy(o => o.ProcessDate))
|
||||
|
||||
@@ -9,6 +9,8 @@ using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence;
|
||||
@@ -73,6 +75,68 @@ namespace Umbraco.Core.Services
|
||||
_repositoryFactory = repositoryFactory;
|
||||
_dataTypeService = dataTypeService;
|
||||
}
|
||||
|
||||
public IContent CreateContentVariant(IContent masterContent, string variantKey, bool copyPropertyData = true, int userId = 0)
|
||||
{
|
||||
Mandate.ParameterNotNull(masterContent, "masterContent");
|
||||
Mandate.ParameterNotNullOrEmpty(variantKey, "variantKey");
|
||||
|
||||
var contentType = masterContent.ContentType;
|
||||
|
||||
Content content;
|
||||
if (copyPropertyData)
|
||||
{
|
||||
content = (Content)masterContent.DeepCloneWithResetIdentities();
|
||||
// A copy should never be set to published automatically even if the original was.
|
||||
content.ChangePublishedState(PublishedState.Unpublished);
|
||||
content.VariantInfo = new VariantInfo(masterContent.Id, variantKey);
|
||||
|
||||
if (Copying.IsRaisedEventCancelled(new CopyEventArgs<IContent>(masterContent, content, masterContent.ParentId), this))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
content = new Content(masterContent.Name, masterContent.ParentId, contentType, new PropertyCollection(), new VariantInfo(masterContent.Id, variantKey))
|
||||
{
|
||||
CreatorId = userId,
|
||||
WriterId = userId
|
||||
};
|
||||
|
||||
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IContent>(content), this))
|
||||
{
|
||||
content.WasCancelled = true;
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var repository = _repositoryFactory.CreateContentRepository(uow))
|
||||
{
|
||||
content.CreatorId = userId;
|
||||
content.WriterId = userId;
|
||||
repository.AddOrUpdate(content);
|
||||
//Generate a new preview
|
||||
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, c));
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
if (copyPropertyData)
|
||||
{
|
||||
Copied.RaiseEvent(new CopyEventArgs<IContent>(masterContent, content, false, masterContent.ParentId), this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Created.RaiseEvent(new NewEventArgs<IContent>(content, false, contentType.Alias, masterContent.ParentId), this);
|
||||
Saved.RaiseEvent(new SaveEventArgs<IContent>(content, false), this);
|
||||
}
|
||||
|
||||
Audit.Add(AuditTypes.New, string.Format("Content variant '{0}' was created", content.Name), content.CreatorId, content.Id);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a single permission to the current content item for the specified user ids
|
||||
@@ -216,10 +280,10 @@ namespace Umbraco.Core.Services
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
Saved.RaiseEvent(new SaveEventArgs<IContent>(content, false), this);
|
||||
|
||||
Created.RaiseEvent(new NewEventArgs<IContent>(content, false, contentTypeAlias, parentId), this);
|
||||
|
||||
Saved.RaiseEvent(new SaveEventArgs<IContent>(content, false), this);
|
||||
|
||||
Audit.Add(AuditTypes.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id);
|
||||
|
||||
return content;
|
||||
@@ -297,6 +361,9 @@ namespace Umbraco.Core.Services
|
||||
/// <returns><see cref="IContent"/></returns>
|
||||
public IEnumerable<IContent> GetByIds(IEnumerable<int> ids)
|
||||
{
|
||||
//if we don't do this, it will return ALL of the content
|
||||
if (ids.Any() == false) return Enumerable.Empty<IContent>();
|
||||
|
||||
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetAll(ids.ToArray());
|
||||
@@ -1599,11 +1666,10 @@ namespace Umbraco.Core.Services
|
||||
x => x.Success || x.Result.StatusType == PublishStatusType.SuccessAlreadyPublished))
|
||||
{
|
||||
item.Result.ContentItem.WriterId = userId;
|
||||
repository.AddOrUpdate(item.Result.ContentItem);
|
||||
//add or update a preview
|
||||
repository.AddOrUpdatePreviewXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, c));
|
||||
//add or update the published xml
|
||||
repository.AddOrUpdateContentXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, c));
|
||||
|
||||
//add or update the item, xml structures and variants
|
||||
AddOrUpdate(repository, item.Result.ContentItem, true);
|
||||
|
||||
updated.Add(item.Result.ContentItem);
|
||||
}
|
||||
|
||||
@@ -1710,17 +1776,9 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
content.WriterId = userId;
|
||||
|
||||
repository.AddOrUpdate(content);
|
||||
|
||||
//Generate a new preview
|
||||
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, c));
|
||||
//add or update content, xml structures and variants
|
||||
AddOrUpdate(repository, content, published);
|
||||
|
||||
if (published)
|
||||
{
|
||||
//Content Xml
|
||||
repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, c));
|
||||
}
|
||||
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
@@ -1878,6 +1936,47 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
private void AddOrUpdate(IContentRepository repository, IContent content, bool isPublished)
|
||||
{
|
||||
//add or update the item
|
||||
repository.AddOrUpdate(content);
|
||||
//add or update a preview
|
||||
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, c));
|
||||
if (isPublished)
|
||||
{
|
||||
//add or update the published xml
|
||||
repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, c));
|
||||
}
|
||||
|
||||
//If the master name has changed, then change all variants too.
|
||||
// NOTE: We do this to keep the variant document name's in-sync with the master
|
||||
// when we do an AddOrUpdate on a variant in the repository it will always ensure to
|
||||
// set it's name to it's master
|
||||
if (content.VariantInfo.IsVariant == false)
|
||||
{
|
||||
if (((ICanBeDirty)content).IsPropertyDirty("Name"))
|
||||
{
|
||||
if (content.VariantInfo.VariantIds.Any())
|
||||
{
|
||||
var variants = repository.GetAll(content.VariantInfo.VariantIds);
|
||||
foreach (var variant in variants)
|
||||
{
|
||||
//add or update the item
|
||||
repository.AddOrUpdate(variant);
|
||||
//add or update a preview
|
||||
repository.AddOrUpdatePreviewXml(variant, c => _entitySerializer.Serialize(this, _dataTypeService, c));
|
||||
if (isPublished)
|
||||
{
|
||||
//add or update the published xml
|
||||
repository.AddOrUpdateContentXml(variant, c => _entitySerializer.Serialize(this, _dataTypeService, c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Proxy Event Handlers
|
||||
|
||||
@@ -626,6 +626,7 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
strictSchemaBuilder.AppendLine(String.Format("<!ELEMENT {0} ANY>", safeAlias));
|
||||
strictSchemaBuilder.AppendLine(String.Format("<!ATTLIST {0} id ID #REQUIRED>", safeAlias));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,15 @@ namespace Umbraco.Core.Services
|
||||
xml.Add(new XAttribute("template", content.Template == null ? "0" : content.Template.Id.ToString(CultureInfo.InvariantCulture)));
|
||||
xml.Add(new XAttribute("nodeTypeAlias", content.ContentType.Alias));
|
||||
|
||||
if (content.VariantInfo.IsVariant)
|
||||
{
|
||||
//normally this is empty, now we can use the value
|
||||
xml.Attribute("isDoc").Value = "variant";
|
||||
|
||||
xml.Add(new XAttribute("masterDocId", content.VariantInfo.MasterDocId));
|
||||
xml.Add(new XAttribute("variantKey", content.VariantInfo.Key));
|
||||
}
|
||||
|
||||
if (deep)
|
||||
{
|
||||
var descendants = contentService.GetDescendants(content).ToArray();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Publishing;
|
||||
|
||||
@@ -11,6 +12,9 @@ namespace Umbraco.Core.Services
|
||||
/// </summary>
|
||||
public interface IContentService : IService
|
||||
{
|
||||
|
||||
IContent CreateContentVariant(IContent masterContent, string variantKey, bool copyPropertyData = true, int userId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a single permission to the current content item for the specified user ids
|
||||
/// </summary>
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="ids">Optional array of integer ids to return relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
IEnumerable<IRelation> GetAllRelations(params int[] ids);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>
|
||||
/// </summary>
|
||||
@@ -63,6 +63,30 @@ namespace Umbraco.Core.Services
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
IEnumerable<IRelation> GetByParentId(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their parent Id for a certain relation type
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the parent to retrieve relations for</param>
|
||||
/// <param name="relationTypeAlias"></param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
IEnumerable<IRelation> GetByParentId(int id, string relationTypeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by any of parent Ids for a certain relation type
|
||||
/// </summary>
|
||||
/// <param name="ids">Ids of any parent to retrieve relations for</param>
|
||||
/// <param name="relationTypeAlias"></param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
IEnumerable<IRelation> GetByParentIds(int[] ids, string relationTypeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by any of child Ids for a certain relation type
|
||||
/// </summary>
|
||||
/// <param name="ids">Ids of any child to retrieve relations for</param>
|
||||
/// <param name="relationTypeAlias"></param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
IEnumerable<IRelation> GetByChildIds(int[] ids, string relationTypeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their parent entity
|
||||
/// </summary>
|
||||
@@ -108,6 +132,15 @@ namespace Umbraco.Core.Services
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
IEnumerable<IRelation> GetByParentOrChildId(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their child or parent Id.
|
||||
/// Using this method will get you all relations regards of it being a child or parent relation.
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the child or parent to retrieve relations for</param>
|
||||
/// <param name="relationTypeAlias"></param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
IEnumerable<IRelation> GetByParentOrChildId(int id, string relationTypeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by the Name of the <see cref="RelationType"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -128,6 +128,30 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRelation> GetByParentId(int id, string relationTypeAlias)
|
||||
{
|
||||
using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetByParentId(id, relationTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRelation> GetByParentIds(int[] ids, string relationTypeAlias)
|
||||
{
|
||||
using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetByParentIds(ids, relationTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRelation> GetByChildIds(int[] ids, string relationTypeAlias)
|
||||
{
|
||||
using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetByChildIds(ids, relationTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their parent entity
|
||||
/// </summary>
|
||||
@@ -146,6 +170,7 @@ namespace Umbraco.Core.Services
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent, string relationTypeAlias)
|
||||
{
|
||||
//TODO: Fix this, it should be filtering via SQL
|
||||
return GetByParent(parent).Where(relation => relation.RelationType.Alias == relationTypeAlias);
|
||||
}
|
||||
|
||||
@@ -199,6 +224,14 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRelation> GetByParentOrChildId(int id, string relationTypeAlias)
|
||||
{
|
||||
using (var repository = _repositoryFactory.CreateRelationRepository(_uowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetByParentOrChildId(id, relationTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by the Name of the <see cref="RelationType"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -338,6 +338,7 @@
|
||||
<Compile Include="Models\ContentTypeCompositionBase.cs" />
|
||||
<Compile Include="Models\ContentTypeExtensions.cs" />
|
||||
<Compile Include="Models\ContentTypeSort.cs" />
|
||||
<Compile Include="Models\ContentVariations\VariantInfo.cs" />
|
||||
<Compile Include="Models\ContentXmlEntity.cs" />
|
||||
<Compile Include="Models\DeepCloneHelper.cs" />
|
||||
<Compile Include="Models\IDeepCloneable.cs" />
|
||||
@@ -1174,7 +1175,9 @@
|
||||
<ItemGroup>
|
||||
<Content Include="Strings\Notes.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<Folder Include="Persistence\Migrations\Upgrades\TargetVersionEight\" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
||||
@@ -289,5 +289,19 @@ namespace Umbraco.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an XContainer to an XmlNode for the given xml document
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <param name="xmlDoc"></param>
|
||||
/// <returns></returns>
|
||||
public static XmlNode ToXmlNode(this XContainer element, XmlDocument xmlDoc)
|
||||
{
|
||||
using (XmlReader xmlReader = element.CreateReader())
|
||||
{
|
||||
return xmlDoc.ReadNode(xmlReader);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -400,5 +400,21 @@ namespace Umbraco.Core
|
||||
var d = m.Cast<Match>().ToDictionary(attributeSet => attributeSet.Groups["attributeName"].Value.ToString().ToLower(), attributeSet => attributeSet.Groups["attributeValue"].Value.ToString());
|
||||
return d;
|
||||
}
|
||||
|
||||
public static XmlElement CloneElement(XmlElement current, XmlDocument doc, string prefix, string ns)
|
||||
{
|
||||
var clone = doc.CreateElement(prefix, current.LocalName, ns);
|
||||
|
||||
foreach (XmlAttribute att in current.Attributes)
|
||||
clone.SetAttribute(att.Name, att.Value);
|
||||
|
||||
foreach (XmlElement el in current.ChildNodes)
|
||||
{
|
||||
var newDatael = doc.ImportNode(el, true);
|
||||
clone.AppendChild(newDatael);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using System.Xml;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
using Umbraco.Web.Security;
|
||||
using umbraco.BusinessLogic;
|
||||
using Umbraco.Core;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
{
|
||||
//[TestFixture]
|
||||
//public class ContentSegmentProviderTests
|
||||
//{
|
||||
|
||||
// [Test]
|
||||
// public void Get_Advertised_Segments()
|
||||
// {
|
||||
// var provider = new MyTestProvider();
|
||||
|
||||
// Assert.AreEqual(3, provider.SegmentKeysToPersist.Count());
|
||||
// }
|
||||
|
||||
|
||||
// private class MyTestProvider : ContentSegmentProvider
|
||||
// {
|
||||
// public override SegmentCollection GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest)
|
||||
// {
|
||||
// return new SegmentCollection(Enumerable.Empty<Segment>());
|
||||
// }
|
||||
// }
|
||||
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContentSegmentsTests
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void Get_All_Segments_For_Request()
|
||||
{
|
||||
var result = RequestSegments.GetAllSegmentsForRequest(new ContentSegmentProvider[]
|
||||
{
|
||||
new MyTestProvider1(),
|
||||
new MyTestProvider2()
|
||||
}, new Uri("http://localhost/test?blah=1"), new Uri("http://localhost/test/"),
|
||||
Mock.Of<HttpRequestBase>(req => req.Cookies == new HttpCookieCollection()),
|
||||
new Dictionary<string, bool>
|
||||
{
|
||||
{typeof(MyTestProvider1).FullName, true},
|
||||
{typeof(MyTestProvider2).FullName, true}
|
||||
});
|
||||
|
||||
Assert.AreEqual(4, result.Count());
|
||||
Assert.AreEqual(true, result["key1"].Value);
|
||||
Assert.AreEqual("blah", result["key2"].Value);
|
||||
Assert.AreEqual(9876, result["key3"].Value);
|
||||
Assert.AreEqual(123, result["key4"].Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_All_Segments_For_Request_With_Approval()
|
||||
{
|
||||
var result = RequestSegments.GetAllSegmentsForRequest(new ContentSegmentProvider[]
|
||||
{
|
||||
new MyTestProvider1(),
|
||||
new MyTestProvider2()
|
||||
}, new Uri("http://localhost/test?blah=1"), new Uri("http://localhost/test/"),
|
||||
Mock.Of<HttpRequestBase>(req => req.Cookies == new HttpCookieCollection()),
|
||||
new Dictionary<string, bool>
|
||||
{
|
||||
{typeof(MyTestProvider1).FullName, true},
|
||||
{typeof(MyTestProvider2).FullName, false}
|
||||
});
|
||||
|
||||
Assert.AreEqual(3, result.Count());
|
||||
}
|
||||
|
||||
private class MyTestProvider1 : ContentSegmentProvider
|
||||
{
|
||||
public override SegmentCollection GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest)
|
||||
{
|
||||
return new SegmentCollection(new List<Segment>
|
||||
{
|
||||
new Segment("key1", true),
|
||||
new Segment("key2", "blahdd"),
|
||||
new Segment("key3", 9876)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class MyTestProvider2 : ContentSegmentProvider
|
||||
{
|
||||
public override SegmentCollection GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest)
|
||||
{
|
||||
return new SegmentCollection(new List<Segment>
|
||||
{
|
||||
new Segment("key1", true),
|
||||
new Segment("key2", "blah"),
|
||||
new Segment("key4", 123)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Moq;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
@@ -6,6 +7,7 @@ using Umbraco.Tests.TestHelpers.Stubs;
|
||||
using Umbraco.Web.Routing;
|
||||
using umbraco.BusinessLogic;
|
||||
using umbraco.cms.businesslogic.template;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using Umbraco.Tests.TestHelpers.Stubs;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.PublishedCache.XmlPublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
|
||||
namespace Umbraco.Tests.TestHelpers
|
||||
{
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
<Compile Include="AngularIntegration\JsInitializationTests.cs" />
|
||||
<Compile Include="AngularIntegration\ServerVariablesParserTests.cs" />
|
||||
<Compile Include="AttemptTests.cs" />
|
||||
<Compile Include="Routing\ContentSegmentProviderTests.cs" />
|
||||
<Compile Include="Membership\DynamicMemberContentTests.cs" />
|
||||
<Compile Include="Membership\MembershipProviderBaseTests.cs" />
|
||||
<Compile Include="Membership\UmbracoServiceMembershipProviderTests.cs" />
|
||||
@@ -317,6 +318,7 @@
|
||||
<Compile Include="PublishedContent\StronglyTypedModels\Textpage.cs" />
|
||||
<Compile Include="PublishedContent\StronglyTypedModels\TypedModelBase.cs" />
|
||||
<Compile Include="PublishedContent\StronglyTypedModels\UmbracoTemplatePage`T.cs" />
|
||||
<Compile Include="Routing\ContentSegmentsTests.cs" />
|
||||
<Compile Include="Services\LocalizationServiceTests.cs" />
|
||||
<Compile Include="Resolvers\XsltExtensionsResolverTests.cs" />
|
||||
<Compile Include="Services\MemberServiceTests.cs" />
|
||||
|
||||
@@ -4,6 +4,7 @@ module.exports = function (grunt) {
|
||||
// Default task.
|
||||
grunt.registerTask('default', ['jshint:dev','build','karma:unit']);
|
||||
grunt.registerTask('dev', ['jshint:dev', 'build', 'webserver', 'open:dev', 'watch']);
|
||||
grunt.registerTask('vs', ['jshint:dev', 'build', 'watch']);
|
||||
|
||||
//run by the watch task
|
||||
grunt.registerTask('watch-js', ['jshint:dev','concat','copy:app','copy:mocks','copy:packages','copy:vs','karma:unit']);
|
||||
|
||||
+21
-6
@@ -15,11 +15,13 @@ angular.module("umbraco.directives")
|
||||
templateUrl: 'views/directives/umb-content-name.html',
|
||||
scope: {
|
||||
placeholder: '@placeholder',
|
||||
model: '=ngModel'
|
||||
model: '=ngModel',
|
||||
readonly: '='
|
||||
},
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
|
||||
var inputElement = element.find("input");
|
||||
var inputElement = null;
|
||||
|
||||
if(scope.placeholder && scope.placeholder[0] === "@"){
|
||||
localizationService.localize(scope.placeholder.substring(1))
|
||||
.then(function(value){
|
||||
@@ -39,6 +41,12 @@ angular.module("umbraco.directives")
|
||||
}
|
||||
|
||||
var mouseMoveDebounce = _.throttle(function (e) {
|
||||
|
||||
//don't process if it's null
|
||||
if (!inputElement || inputElement.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mX = e.pageX;
|
||||
mY = e.pageY;
|
||||
// not focused and not over element
|
||||
@@ -62,18 +70,25 @@ angular.module("umbraco.directives")
|
||||
|
||||
})();
|
||||
|
||||
$timeout(function(){
|
||||
$timeout(function () {
|
||||
|
||||
inputElement = element.find("input");
|
||||
|
||||
if(!scope.model){
|
||||
scope.goEdit();
|
||||
}
|
||||
|
||||
}, 100, false);
|
||||
|
||||
scope.goEdit = function(){
|
||||
scope.editMode = true;
|
||||
|
||||
$timeout(function () {
|
||||
inputElement.focus();
|
||||
}, 100, false);
|
||||
if (inputElement && inputElement.length > 0) {
|
||||
$timeout(function () {
|
||||
inputElement.focus();
|
||||
}, 100, false);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
scope.exitEdit = function(){
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* @name umbraco.directives.directive:preventDefault
|
||||
**/
|
||||
angular.module("umbraco.directives")
|
||||
.directive('preventDefault', function() {
|
||||
return function(scope, element, attrs) {
|
||||
.directive('preventDefault', function () {
|
||||
return function (scope, element, attrs) {
|
||||
|
||||
var enabled = true;
|
||||
//check if there's a value for the attribute, if there is and it's false then we conditionally don't
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:stopPropagation
|
||||
**/
|
||||
angular.module("umbraco.directives")
|
||||
.directive('stopPropagation', function () {
|
||||
return function (scope, element, attrs) {
|
||||
|
||||
var enabled = true;
|
||||
//check if there's a value for the attribute, if there is and it's false then we conditionally don't
|
||||
//stop propogation
|
||||
if (attrs.preventDefault) {
|
||||
attrs.$observe("stopPropagation", function (newVal) {
|
||||
enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true;
|
||||
});
|
||||
}
|
||||
|
||||
$(element).click(function (event) {
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (enabled === true) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -8,29 +8,33 @@
|
||||
**/
|
||||
function valTab() {
|
||||
return {
|
||||
require: "^form",
|
||||
//this means the form is optional and will not throw an exception if not found
|
||||
// if the form is not found, then this directive will not do anything
|
||||
require: "^?form",
|
||||
restrict: "A",
|
||||
link: function (scope, element, attr, formCtrl) {
|
||||
|
||||
var tabId = "tab" + scope.tab.id;
|
||||
|
||||
scope.tabHasError = false;
|
||||
if (formCtrl) {
|
||||
var tabId = "tab" + scope.tab.id;
|
||||
|
||||
//listen for form validation changes
|
||||
scope.$on("valStatusChanged", function(evt, args) {
|
||||
if (!args.form.$valid) {
|
||||
var tabContent = element.closest(".umb-panel").find("#" + tabId);
|
||||
//check if the validation messages are contained inside of this tabs
|
||||
if (tabContent.find(".ng-invalid").length > 0) {
|
||||
scope.tabHasError = true;
|
||||
} else {
|
||||
scope.tabHasError = false;
|
||||
|
||||
//listen for form validation changes
|
||||
scope.$on("valStatusChanged", function (evt, args) {
|
||||
if (!args.form.$valid) {
|
||||
var tabContent = element.closest(".umb-panel").find("#" + tabId);
|
||||
//check if the validation messages are contained inside of this tabs
|
||||
if (tabContent.find(".ng-invalid").length > 0) {
|
||||
scope.tabHasError = true;
|
||||
} else {
|
||||
scope.tabHasError = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
scope.tabHasError = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
scope.tabHasError = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -630,6 +630,23 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
[{ id: id }])),
|
||||
'Failed to publish content with id ' + id);
|
||||
|
||||
},
|
||||
|
||||
createVariant: function(id, key) {
|
||||
if (!id) {
|
||||
throw "id cannot be null";
|
||||
}
|
||||
if (!key) {
|
||||
throw "key cannot be null";
|
||||
}
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"PostCreateVariant",
|
||||
[{ id: id }, {key: key}])),
|
||||
'Failed to create a content variant for master doc id ' + id + " and key " + key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -276,11 +276,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
|
||||
* @param {object} treeNode the node to remove
|
||||
*/
|
||||
removeNode: function(treeNode) {
|
||||
if (treeNode.parent() == null) {
|
||||
throw "Cannot remove a node that doesn't have a parent";
|
||||
if (angular.isFunction(treeNode.parent) && treeNode.parent() != null) {
|
||||
//remove the current item from it's siblings
|
||||
treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1);
|
||||
}
|
||||
//remove the current item from it's siblings
|
||||
treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,4 +83,5 @@
|
||||
@import "property-editors.less";
|
||||
|
||||
@import "typeahead.less";
|
||||
@import "dashboards.less";
|
||||
@import "hacks.less";
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
table.segment-dashboard td:first-child{
|
||||
width:85px;
|
||||
}
|
||||
table.segment-dashboard td:nth-child(2) {
|
||||
width:500px;
|
||||
}
|
||||
table.segment-dashboard td small span {
|
||||
color:@grayLight;
|
||||
}
|
||||
table.segment-dashboard td small {
|
||||
color:@gray;
|
||||
}
|
||||
|
||||
.segment-config .umb-pane {
|
||||
border-bottom:solid @gray 1px;
|
||||
position:relative;
|
||||
}
|
||||
.segment-config .umb-pane button {
|
||||
top: 4px;
|
||||
left: 550px;
|
||||
position: absolute;
|
||||
}
|
||||
@@ -84,4 +84,9 @@ iframe, .content-column-body {
|
||||
}
|
||||
.icon-chevron-down:before {
|
||||
content: "\e0c9";
|
||||
}
|
||||
|
||||
.caret.caret-reversed {
|
||||
border-top-width: 0;
|
||||
border-bottom: 4px solid #000000;
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.umb-panel-header .span7 {
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.umb-panel-body {
|
||||
top: 101px;
|
||||
left: 0px;
|
||||
@@ -44,15 +48,19 @@
|
||||
margin: 15px 0 0 20px;
|
||||
padding: 3px 5px;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
border: 1px solid @grayLighter;
|
||||
}
|
||||
|
||||
.umb-panel-header .umb-headline:focus,.umb-panel-header .umb-headline:active {
|
||||
.umb-panel-header input.umb-headline:focus,.umb-panel-header input.umb-headline:active {
|
||||
border: 1px solid @grayLight;
|
||||
background-color: @white;
|
||||
}
|
||||
|
||||
.umb-panel-header div.umb-headline:hover,.umb-panel-header div.umb-headline:active {
|
||||
border-bottom: none;
|
||||
color:@gray;
|
||||
}
|
||||
|
||||
.umb-headline-editor-wrapper{
|
||||
position: relative;
|
||||
}
|
||||
@@ -252,4 +260,51 @@
|
||||
|
||||
.umb-panel .text-success { color: @formSuccessText; }
|
||||
.umb-panel a.text-success:hover,
|
||||
.umb-panel a.text-success:focus { color: darken(@formSuccessText, 10%); }
|
||||
.umb-panel a.text-success:focus { color: darken(@formSuccessText, 10%); }
|
||||
|
||||
|
||||
//Don't know where else to put this, putting it here for now, more or less this file seems
|
||||
// to be the stying for 'editors'
|
||||
.umb-segment-selector {
|
||||
position:absolute;
|
||||
left:20px;
|
||||
z-index:2345;
|
||||
top:15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.umb-segment-selector .dropdown-toggle:hover *,
|
||||
.umb-segment-selector .dropdown-toggle{
|
||||
text-decoration: none !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/*.umb-panel-header .umb-content-headline:hover .umb-segment-selector {
|
||||
top:15px;
|
||||
transition: opacity .5s ease-in;
|
||||
opacity: 1;
|
||||
}*/
|
||||
|
||||
.umb-segment-selector ul {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.umb-segment-selector ul i {
|
||||
color:@blue;
|
||||
}
|
||||
|
||||
.umb-segment-selector li.trashed a, .umb-segment-selector li.trashed i, .umb-segment-selector li.trashed span{
|
||||
color:@grayLight;
|
||||
}
|
||||
|
||||
.umb-content-headline > small {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 28px;
|
||||
}
|
||||
.umb-content-headline > small span{
|
||||
color:@blue;
|
||||
}
|
||||
|
||||
@@ -488,3 +488,31 @@ body.touch .umb-actions a{
|
||||
|
||||
body.touch a.umb-options i {margin-top: 20px;}
|
||||
|
||||
|
||||
/* no idea where to put this, for drop down menus */
|
||||
|
||||
.dropdown-menu.umb-actions li{
|
||||
position:relative;
|
||||
}
|
||||
.dropdown-menu.umb-actions li a{
|
||||
padding-left: 50px;
|
||||
}
|
||||
.dropdown-menu.umb-actions li button{
|
||||
margin:10px;
|
||||
}
|
||||
.dropdown-menu.umb-actions li small {
|
||||
display:block;
|
||||
color:@grayLight;
|
||||
}
|
||||
.dropdown-menu.umb-actions li div.checkbox-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
.dropdown-menu.umb-actions li div.checkbox-container input{
|
||||
display: block;
|
||||
margin: 18px;
|
||||
float: none;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<form ng-controller="Umbraco.DashboardController" class="umb-dashboard" val-form-manager>
|
||||
<div ng-controller="Umbraco.DashboardController" class="umb-dashboard">
|
||||
<umb-panel>
|
||||
<umb-header tabs="dashboard.tabs">
|
||||
<div class="umb-headline-editor-wrapper span12">
|
||||
@@ -28,4 +28,4 @@
|
||||
</umb-tab>
|
||||
</umb-tab-view>
|
||||
</umb-panel>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<div ng-controller="Umbraco.Editors.Content.CompareController">
|
||||
|
||||
<div ng-repeat="id in ids" ng-include="'views/content/edit.html'">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name Umbraco.Editors.Content.CompareController
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* The controller for the content variant comparing
|
||||
*/
|
||||
function ContentCompareController($scope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http, $location) {
|
||||
|
||||
//setup scope vars
|
||||
$scope.ids = $routeParams.id.split(",");
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.Content.CompareController", ContentCompareController);
|
||||
@@ -6,7 +6,7 @@
|
||||
* @description
|
||||
* The controller for deleting content
|
||||
*/
|
||||
function ContentDeleteController($scope, contentResource, treeService, navigationService) {
|
||||
function ContentDeleteController($scope, contentResource, treeService, navigationService, $location) {
|
||||
|
||||
$scope.performDelete = function() {
|
||||
|
||||
@@ -24,7 +24,17 @@ function ContentDeleteController($scope, contentResource, treeService, navigatio
|
||||
|
||||
//ensure the recycle bin has child nodes now
|
||||
var recycleBin = treeService.getDescendantNode(rootNode, -20);
|
||||
recycleBin.hasChildren = true;
|
||||
|
||||
//this could be null if the current node is a variant (i.e. it doesn't actually exist in the tree)
|
||||
if (recycleBin) {
|
||||
recycleBin.hasChildren = true;
|
||||
}
|
||||
|
||||
if ($scope.currentNode.metaData && $scope.currentNode.metaData.masterDocId) {
|
||||
//this could be a variant, if it is then we should navigate to it's master
|
||||
$location.path("content/content/edit/" + $scope.currentNode.metaData.masterDocId);
|
||||
}
|
||||
|
||||
|
||||
navigationService.hideMenu();
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @description
|
||||
* The controller for the content editor
|
||||
*/
|
||||
function ContentEditController($scope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) {
|
||||
function ContentEditController($scope, $element, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http, $location) {
|
||||
|
||||
//setup scope vars
|
||||
$scope.defaultButton = null;
|
||||
@@ -15,7 +15,6 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
|
||||
$scope.currentSection = appState.getSectionState("currentSection");
|
||||
$scope.currentNode = null; //the editors affiliated node
|
||||
|
||||
|
||||
//This sets up the action buttons based on what permissions the user has.
|
||||
//The allowedActions parameter contains a list of chars, each represents a button by permission so
|
||||
//here we'll build the buttons according to the chars of the user.
|
||||
@@ -121,8 +120,8 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
|
||||
/** Syncs the content item to it's tree node - this occurs on first load and after saving */
|
||||
function syncTreeNode(content, path, initialLoad) {
|
||||
|
||||
//If this is a child of a list view then we can't actually sync the real tree
|
||||
if (!$scope.content.isChildOfListView) {
|
||||
//If this is a child of a list view or a variant then we can't actually sync the real tree
|
||||
if (!$scope.content.isChildOfListView && !$scope.content.masterDocId) {
|
||||
navigationService.syncTree({ tree: "content", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
|
||||
$scope.currentNode = syncArgs.node;
|
||||
});
|
||||
@@ -201,8 +200,15 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
//we are editing so get the content item from the server
|
||||
contentResource.getById($routeParams.id)
|
||||
|
||||
//When we are comparing content variants, we will render this template in an iteration so we need to check
|
||||
// where to get the id from, either the routeParams or from the current scope
|
||||
|
||||
var id = (angular.isDefined($scope.$index) && angular.isDefined($scope.id)) ? $scope.id : $routeParams.id;
|
||||
|
||||
contentResource.getById(id)
|
||||
.then(function (data) {
|
||||
$scope.loaded = true;
|
||||
$scope.content = data;
|
||||
@@ -219,9 +225,27 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
|
||||
|
||||
syncTreeNode($scope.content, data.path, true);
|
||||
|
||||
});
|
||||
_.each($scope.content.variants, function(i) {
|
||||
i.compare = true;
|
||||
});
|
||||
$scope.showCompare = true;
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
$scope.compareVariants = function () {
|
||||
var ids = _.map($scope.content.variants, function(i) {
|
||||
return i.id;
|
||||
});
|
||||
$location.path("content/content/compare/" + ids.join(","));
|
||||
}
|
||||
|
||||
$scope.toggleCompare = function(variant) {
|
||||
$scope.showCompare = _.find($scope.content.variants, function (i) {
|
||||
return i.compare === true;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.unPublish = function () {
|
||||
|
||||
@@ -284,6 +308,29 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
|
||||
}
|
||||
};
|
||||
|
||||
$scope.selectVariant = function (v)
|
||||
{
|
||||
if (v.exists) {
|
||||
$location.path("content/content/edit/" + v.id);
|
||||
}
|
||||
else {
|
||||
//we need to create it, for now we'll use a normal prompt
|
||||
if (confirm("Are you sure you wish to create this content variant? (" + v.key + ")")) {
|
||||
|
||||
contentResource.createVariant($scope.content.id, v.key)
|
||||
.then(function (newId) {
|
||||
|
||||
$location.path("content/content/edit/" + newId);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.toMasterDoc = function() {
|
||||
$location.path("content/content/edit/" + $scope.content.masterDocId);
|
||||
}
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.Content.EditController", ContentEditController);
|
||||
|
||||
@@ -1,39 +1,122 @@
|
||||
<form novalidate name="contentForm"
|
||||
ng-controller="Umbraco.Editors.Content.EditController"
|
||||
ng-show="loaded"
|
||||
ng-submit="save()"
|
||||
val-form-manager>
|
||||
|
||||
ng-controller="Umbraco.Editors.Content.EditController"
|
||||
ng-show="loaded"
|
||||
ng-submit="save()"
|
||||
val-form-manager>
|
||||
|
||||
<umb-panel ng-class="'editor-breadcrumb'">
|
||||
<umb-header tabs="content.tabs">
|
||||
|
||||
<div class="span7">
|
||||
<umb-content-name
|
||||
placeholder="@placeholders_entername"
|
||||
ng-model="content.name"/>
|
||||
<div class="span7 umb-content-headline">
|
||||
|
||||
<!--
|
||||
<small ng-if="content.masterDocId != null">
|
||||
Variant: <span>{{content.variantKey}}</span>
|
||||
</small>
|
||||
|
||||
<div class="umb-segment-selector" ng-if="content.masterDocId != null">
|
||||
<a class="btn" href="" ng-click="toMasterDoc()">
|
||||
<small>Back to master doc</small>
|
||||
<span class="caret caret-reversed"></span>
|
||||
</a>
|
||||
</div>
|
||||
-->
|
||||
|
||||
|
||||
<div class="umb-segment-selector"
|
||||
ng-if="content.variants.length > 0 || content.masterDocId != null">
|
||||
|
||||
<a href style="float: right" class="btn btn-link dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="icon-globe"></i>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
|
||||
|
||||
<!--
|
||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<small>Variants</small>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
-->
|
||||
<ul class="dropdown-menu umb-actions" role="menu">
|
||||
|
||||
<li class="action seb" ng-if="content.masterDocId != null">
|
||||
<a style="padding-left: 20px" href="" ng-click="toMasterDoc()">
|
||||
<i class="icon icon-reply-arrow"></i>
|
||||
|
||||
<div class="menu-label ng-binding">
|
||||
Back to master document
|
||||
|
||||
<small>
|
||||
|
||||
</small>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li
|
||||
class="action"
|
||||
ng-repeat="item in content.variants"
|
||||
ng-class="{'trashed' : item.isTrashed}">
|
||||
|
||||
<ng-form name="itemCompare">
|
||||
|
||||
<a href=""
|
||||
style="padding-left: 20px"
|
||||
ng-attr-title="{{item.exists ? 'Edit variant' : 'Create variant' }}"
|
||||
ng-click="selectVariant(item)">
|
||||
|
||||
<i class="icon"
|
||||
ng-class="item.isTrashed ? 'icon-trash' : item.exists ? 'icon-edit' : 'icon-add'"></i>
|
||||
|
||||
|
||||
<div class="menu-label ng-binding">
|
||||
{{content.name}} - <span style="opacity: 0.8">{{item.name}}</span>
|
||||
|
||||
<small
|
||||
ng-if="item.lastUpdated">Updated: {{item.lastUpdated | date : 'medium'}}
|
||||
</small>
|
||||
|
||||
<small
|
||||
ng-if="!item.lastUpdated">Create new variant
|
||||
</small>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</ng-form>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<umb-content-name readonly="content.masterDocId"
|
||||
placeholder="@placeholders_entername"
|
||||
ng-model="content.name" />
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="span5">
|
||||
<div class="span5">
|
||||
<div class="btn-toolbar pull-right umb-btn-toolbar">
|
||||
<div class="btn-group" ng-animate="'fade'" ng-show="formStatus">
|
||||
<p class="btn btn-link umb-status-label">{{formStatus}}</p>
|
||||
</div>
|
||||
|
||||
|
||||
<umb-options-menu ng-show="currentNode"
|
||||
current-node="currentNode"
|
||||
current-section="{{currentSection}}">
|
||||
current-node="currentNode"
|
||||
current-section="{{currentSection}}">
|
||||
</umb-options-menu>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</umb-header>
|
||||
|
||||
<umb-tab-view>
|
||||
<umb-tab id="tab{{tab.id}}" rel="{{tab.id}}" ng-repeat="tab in content.tabs">
|
||||
<div class="umb-pane">
|
||||
<umb-property
|
||||
property="property"
|
||||
ng-repeat="property in tab.properties">
|
||||
<umb-property property="property"
|
||||
ng-repeat="property in tab.properties">
|
||||
<umb-editor model="property"></umb-editor>
|
||||
</umb-property>
|
||||
|
||||
@@ -45,19 +128,19 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="btn-group dropup" ng-if="defaultButton" >
|
||||
<div class="btn-group dropup" ng-if="defaultButton">
|
||||
<!-- primary button -->
|
||||
<a class="btn btn-success" href="#" ng-click="performAction(defaultButton)" prevent-default>
|
||||
<localize key="{{defaultButton.labelKey}}">{{defaultButton.labelKey}}</localize>
|
||||
<localize key="{{defaultButton.labelKey}}">{{defaultButton.labelKey}}</localize>
|
||||
</a>
|
||||
|
||||
|
||||
<a class="btn btn-success dropdown-toggle" data-toggle="dropdown" ng-if="subButtons.length > 0">
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<!-- sub buttons -->
|
||||
<ul class="dropdown-menu bottom-up" role="menu" aria-labelledby="dLabel" ng-if="subButtons.length > 0">
|
||||
<ul class="dropdown-menu bottom-up" role="menu" aria-labelledby="dLabel" ng-if="subButtons.length > 0">
|
||||
<li ng-repeat="btn in subButtons">
|
||||
<a href="#" ng-click="performAction(btn)" prevent-default>
|
||||
<localize key="{{btn.labelKey}}">{{btn.labelKey}}</localize>
|
||||
@@ -65,7 +148,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</umb-tab>
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
function segmentDashboardController($scope, umbRequestHelper, $log, $http, formHelper) {
|
||||
|
||||
function refreshData() {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(umbRequestHelper.getApiUrl("segmentDashboardApiBaseUrl", "GetProviders")),
|
||||
"Failed to retrieve provider data")
|
||||
.then(function(data) {
|
||||
$scope.providers = data;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.providers = [];
|
||||
$scope.configuringSegments = false;
|
||||
$scope.providerConfig = {
|
||||
providerName: "",
|
||||
providerTypeName: "",
|
||||
}
|
||||
$scope.segmentConfig = [];
|
||||
$scope.variantConfig = [];
|
||||
|
||||
$scope.toggle = function(provider) {
|
||||
umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl("segmentDashboardApiBaseUrl", "PostToggleProvider",
|
||||
[{ typeName: provider.typeName }])),
|
||||
'Failed to toggle segment provider')
|
||||
.then(function() {
|
||||
//toggle local var
|
||||
provider.enabled = !provider.enabled;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.showSegmentConfig = function (item) {
|
||||
|
||||
$scope.configuringSegments = true;
|
||||
$scope.providerConfig.providerName = item.displayName;
|
||||
$scope.providerConfig.providerTypeName = item.typeName;
|
||||
$scope.segmentConfig = item.segmentConfig;
|
||||
}
|
||||
|
||||
$scope.showVariantConfig = function (item) {
|
||||
|
||||
$scope.configuringVariants = true;
|
||||
$scope.providerConfig.providerName = item.displayName;
|
||||
$scope.providerConfig.providerTypeName = item.typeName;
|
||||
$scope.variantConfig = item.variantConfig;
|
||||
}
|
||||
|
||||
$scope.cancel = function () {
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
|
||||
$scope.configuringVariants = false;
|
||||
$scope.configuringSegments = false;
|
||||
|
||||
$scope.providerConfig = {
|
||||
providerName: "",
|
||||
providerTypeName: "",
|
||||
}
|
||||
$scope.segmentConfig = [];
|
||||
$scope.variantConfig = [];
|
||||
}
|
||||
|
||||
$scope.addItem = function() {
|
||||
$scope.segmentConfig.push({ matchExpression: "", key: "", value: "" });
|
||||
}
|
||||
|
||||
$scope.removeItem = function(item) {
|
||||
$scope.segmentConfig = _.reject($scope.segmentConfig, function (i) { return i === item; });
|
||||
}
|
||||
|
||||
$scope.saveSegmentConfig = function (formCtrl) {
|
||||
|
||||
if (formHelper.submitForm({ scope: $scope, formCtrl: formCtrl })) {
|
||||
|
||||
umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl("segmentDashboardApiBaseUrl", "PostSaveProviderSegmentConfig",
|
||||
[{ typeName: $scope.providerConfig.providerTypeName }]),
|
||||
$scope.segmentConfig),
|
||||
'Failed to save segment configuration')
|
||||
.then(function () {
|
||||
|
||||
//now that its successful, save the persisted values back to the provider's values
|
||||
var provider = _.find($scope.providers, function (i) { return i.typeName === $scope.providerConfig.providerTypeName; });
|
||||
provider.segmentConfig = $scope.segmentConfig;
|
||||
|
||||
refreshData().then(function() {
|
||||
//exit editing config
|
||||
$scope.cancel();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.saveVariantConfig = function (formCtrl) {
|
||||
|
||||
umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl("segmentDashboardApiBaseUrl", "PostSaveProviderVariantConfig",
|
||||
[{ typeName: $scope.providerConfig.providerTypeName }]),
|
||||
$scope.variantConfig),
|
||||
'Failed to save variant configuration')
|
||||
.then(function () {
|
||||
|
||||
//now that its successful, save the persisted values back to the provider's values
|
||||
var provider = _.find($scope.providers, function (i) { return i.typeName === $scope.providerConfig.providerTypeName; });
|
||||
provider.variantConfig = $scope.variantConfig;
|
||||
|
||||
refreshData().then(function () {
|
||||
//exit editing config
|
||||
$scope.cancel();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//initial data load
|
||||
refreshData();
|
||||
}
|
||||
angular.module("umbraco").controller("Umbraco.Dashboard.SegmentDashboard", segmentDashboardController);
|
||||
@@ -0,0 +1,104 @@
|
||||
<div ng-controller="Umbraco.Dashboard.SegmentDashboard">
|
||||
<table class="table segment-dashboard" ng-hide="configuringSegments || configuringVariants">
|
||||
<tr ng-repeat="item in providers">
|
||||
<td>
|
||||
<button class="btn btn-primary" ng-if="!item.enabled" ng-click="toggle(item)">Enable</button>
|
||||
<button class="btn btn-danger" ng-if="item.enabled" ng-click="toggle(item)">Disable</button>
|
||||
</td>
|
||||
<td>
|
||||
<strong title="{{item.typeName}}">{{item.displayName}}</strong>
|
||||
<p>
|
||||
<small>{{item.description}}</small>
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn" ng-if="item.variantConfig.length > 0" ng-click="showVariantConfig(item)">Configure variants</button>
|
||||
<button class="btn" ng-if="item.configurable" ng-click="showSegmentConfig(item)">Configure segments</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div ng-show="configuringVariants" >
|
||||
<h3>'{{providerConfig.providerName}}' Variant Configuration</h3>
|
||||
<p>
|
||||
<small>
|
||||
Here you can enable/disable content variants exposed by this provider
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<form name="segmentVariantForm" novalidate val-form-manager>
|
||||
|
||||
<div ng-repeat="item in variantConfig">
|
||||
|
||||
<umb-pane>
|
||||
|
||||
<umb-control-group label="{{item.name}}" alias="{{item.key}}" description="Variant key: {{item.key}}">
|
||||
<input type="checkbox" name="{{item.key}}" ng-model="$parent.item.enabled" />
|
||||
</umb-control-group>
|
||||
|
||||
</umb-pane>
|
||||
|
||||
</div>
|
||||
|
||||
<button class="btn" ng-click="cancel()">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" ng-click="saveVariantConfig(segmentVariantForm)">Save</button>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-show="configuringSegments" class="segment-config">
|
||||
|
||||
<h3>'{{providerConfig.providerName}}' Segment Configuration</h3>
|
||||
<p>
|
||||
<small>
|
||||
Here you can configure segment keys to assign to a request for this provider
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<form name="segmentConfigForm" novalidate val-form-manager>
|
||||
|
||||
<button class="btn" ng-click="addItem()">Add</button>
|
||||
|
||||
<div ng-repeat="item in segmentConfig">
|
||||
|
||||
<umb-pane>
|
||||
|
||||
<ng-form name="segmentItemConfigForm">
|
||||
|
||||
<umb-control-group label="Match Expression" alias="matchExpression" description="The expression used to match against the value provided by the provider">
|
||||
<input type="text" name="matchExpression" ng-model="$parent.item.matchExpression" required />
|
||||
<span class="help-inline" val-msg-for="matchExpression" val-toggle-msg="required">Required</span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="Segment Key" alias="key" description="The key to store for this segment match">
|
||||
<input type="text" name="key" ng-model="$parent.item.key" required />
|
||||
<span class="help-inline" val-msg-for="key" val-toggle-msg="required">Required</span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="Persist segment" alias="value" description="This might be useful if you want to track an assigned segment">
|
||||
<input type="checkbox" name="persist" ng-model="$parent.item.persist" title=" For example, if it's a referral provider a segment might be set when coming from 'mysite.com' and you want to know about that later on when the segment no longer exists in the request." />
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="Use as a Content Variant" alias="value" description="If checked, this segment key can be used to create custom Content Variants">
|
||||
<input type="checkbox" name="persist" ng-model="$parent.item.asVariant" />
|
||||
</umb-control-group>
|
||||
|
||||
</ng-form>
|
||||
|
||||
<button class="btn btn-danger btn-mini" ng-click="removeItem(item)">Remove</button>
|
||||
|
||||
</umb-pane>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<button class="btn" ng-click="cancel()">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" ng-click="saveSegmentConfig(segmentConfigForm)">Save</button>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1,19 +1,25 @@
|
||||
<ng-form name="contentNameForm">
|
||||
<div class="umb-headline-editor-wrapper" ng-class="{'ng-invalid': contentNameForm.name.$invalid}">
|
||||
|
||||
<input type="text"
|
||||
name="name"
|
||||
localize="placeholder"
|
||||
class="umb-headline"
|
||||
localize="placeholder"
|
||||
select-on-focus
|
||||
placeholder="{{placeholder}}"
|
||||
ng-model="model"
|
||||
required
|
||||
val-server-field="Name"/>
|
||||
<div class="umb-headline" ng-if="readonly">
|
||||
{{model}}
|
||||
</div>
|
||||
|
||||
<div ng-if ="!readonly">
|
||||
<input type="text"
|
||||
name="name"
|
||||
localize="placeholder"
|
||||
class="umb-headline input-block-level"
|
||||
localize="placeholder"
|
||||
select-on-focus
|
||||
placeholder="{{placeholder}}"
|
||||
ng-model="$parent.model"
|
||||
required
|
||||
val-server-field="Name" />
|
||||
|
||||
<span class="help-inline" val-msg-for="name" val-toggle-msg="required">Required</span>
|
||||
<span class="help-inline" val-msg-for="name" val-toggle-msg="valServerField">{{contentNameForm.name.errorMsg}}</span>
|
||||
<span class="help-inline" val-msg-for="name" val-toggle-msg="required">Required</span>
|
||||
<span class="help-inline" val-msg-for="name" val-toggle-msg="valServerField">{{contentNameForm.name.errorMsg}}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-form>
|
||||
@@ -198,7 +198,10 @@ angular.module("umbraco")
|
||||
tinymce.DOM.events.domLoaded = true;
|
||||
tinymce.init(baseLineConfigObj);
|
||||
|
||||
$scope.isLoading = false;
|
||||
angularHelper.safeApply($scope, function() {
|
||||
$scope.isLoading = false;
|
||||
});
|
||||
|
||||
|
||||
}, 200, false);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ NOTES:
|
||||
* Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config
|
||||
* A new version will invalidate both client and server cache and create new persisted files
|
||||
-->
|
||||
<clientDependency version="1469612626" fileDependencyExtensions=".js,.css" loggerType="Umbraco.Web.UI.CdfLogger, umbraco">
|
||||
<clientDependency version="1963089493" fileDependencyExtensions=".js,.css" loggerType="Umbraco.Web.UI.CdfLogger, umbraco">
|
||||
|
||||
<!--
|
||||
This section is used for Web Forms only, the enableCompositeFiles="true" is optional and by default is set to true.
|
||||
|
||||
@@ -67,4 +67,15 @@
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
|
||||
<section alias="SegmentDashboard">
|
||||
<areas>
|
||||
<area>settings</area>
|
||||
</areas>
|
||||
<tab caption="Content Segmentation">
|
||||
<control showOnce="false" addPanel="true" panelCaption="">
|
||||
views/dashboard/settings/segmentdashboard.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
</dashBoard>
|
||||
@@ -162,7 +162,7 @@ namespace Umbraco.Web.Cache
|
||||
{
|
||||
if (e.RecycleBinEmptiedSuccessfully && e.IsContentRecycleBin)
|
||||
{
|
||||
DistributedCache.Instance.RemoveUnpublishedCachePermanently(e.Ids.ToArray());
|
||||
DistributedCache.Instance.RemoveUnpublishedPageCache(e.Ids.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,8 +177,12 @@ namespace Umbraco.Web.Cache
|
||||
/// </remarks>
|
||||
static void ContentServiceTrashed(IContentService sender, MoveEventArgs<IContent> e)
|
||||
{
|
||||
DistributedCache.Instance.RefreshUnpublishedPageCache(
|
||||
e.MoveInfoCollection.Select(x => x.Entity).ToArray());
|
||||
var ids = e.MoveInfoCollection.Select(x => x.Entity.Id).ToList();
|
||||
//ensure the variants are refreshed too
|
||||
ids.AddRange(e.MoveInfoCollection.SelectMany(x => x.Entity.VariantInfo.VariantIds));
|
||||
ids.AddRange(e.MoveInfoCollection.Select(x => x.Entity.VariantInfo.MasterDocId));
|
||||
|
||||
DistributedCache.Instance.RefreshUnpublishedPageCache(ids.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -200,7 +204,13 @@ namespace Umbraco.Web.Cache
|
||||
}
|
||||
|
||||
//run the un-published cache refresher since copied content is not published
|
||||
DistributedCache.Instance.RefreshUnpublishedPageCache(e.Copy);
|
||||
|
||||
var ids = new List<int> {e.Copy.Id};
|
||||
//ensure the variants are refreshed too
|
||||
ids.AddRange(e.Copy.VariantInfo.VariantIds);
|
||||
ids.Add(e.Copy.VariantInfo.MasterDocId);
|
||||
|
||||
DistributedCache.Instance.RefreshUnpublishedPageCache(ids.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -210,7 +220,12 @@ namespace Umbraco.Web.Cache
|
||||
/// <param name="e"></param>
|
||||
static void ContentServiceDeleted(IContentService sender, DeleteEventArgs<IContent> e)
|
||||
{
|
||||
DistributedCache.Instance.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray());
|
||||
var ids = e.DeletedEntities.Select(x => x.Id).ToList();
|
||||
//ensure the variants are refreshed too
|
||||
ids.AddRange(e.DeletedEntities.SelectMany(x => x.VariantInfo.VariantIds));
|
||||
ids.AddRange(e.DeletedEntities.Select(x => x.VariantInfo.MasterDocId));
|
||||
|
||||
DistributedCache.Instance.RemoveUnpublishedPageCache(ids.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -249,9 +264,14 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
//filter out the entities that have only been saved (not newly published) since
|
||||
// newly published ones will be synced with the published page cache refresher
|
||||
var unpublished = e.SavedEntities.Where(x => x.JustPublished() == false);
|
||||
var unpublished = e.SavedEntities.Where(x => x.JustPublished() == false).ToArray();
|
||||
var ids = unpublished.Select(x => x.Id).ToList();
|
||||
//ensure the variants are refreshed too
|
||||
ids.AddRange(unpublished.SelectMany(x => x.VariantInfo.VariantIds));
|
||||
ids.AddRange(unpublished.Select(x => x.VariantInfo.MasterDocId));
|
||||
|
||||
//run the un-published cache refresher
|
||||
DistributedCache.Instance.RefreshUnpublishedPageCache(unpublished.ToArray());
|
||||
DistributedCache.Instance.RefreshUnpublishedPageCache(ids.ToArray());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -206,75 +206,48 @@ namespace Umbraco.Web.Cache
|
||||
dc.RefreshAll(new Guid(DistributedCache.PageCacheRefresherId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the cache amongst servers for a page
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="documentId"></param>
|
||||
public static void RefreshPageCache(this DistributedCache dc, int documentId)
|
||||
{
|
||||
dc.Refresh(new Guid(DistributedCache.PageCacheRefresherId), documentId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes page cache for all instances passed in
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="content"></param>
|
||||
public static void RefreshPageCache(this DistributedCache dc, params IContent[] content)
|
||||
/// <param name="contentIds"></param>
|
||||
public static void RefreshPageCache(this DistributedCache dc, params int[] contentIds)
|
||||
{
|
||||
dc.Refresh(new Guid(DistributedCache.PageCacheRefresherId), x => x.Id, content);
|
||||
dc.RefreshByJson(new Guid(DistributedCache.PageCacheRefresherId),
|
||||
PageCacheRefresher.SerializeToJsonPayload(PageCacheRefresher.OperationType.Saved, contentIds));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the cache amongst servers for a page
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="content"></param>
|
||||
public static void RemovePageCache(this DistributedCache dc, params IContent[] content)
|
||||
{
|
||||
dc.Remove(new Guid(DistributedCache.PageCacheRefresherId), x => x.Id, content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the cache amongst servers for a page
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="documentId"></param>
|
||||
public static void RemovePageCache(this DistributedCache dc, int documentId)
|
||||
{
|
||||
dc.Remove(new Guid(DistributedCache.PageCacheRefresherId), documentId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// invokes the unpublished page cache refresher
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="content"></param>
|
||||
public static void RefreshUnpublishedPageCache(this DistributedCache dc, params IContent[] content)
|
||||
{
|
||||
dc.Refresh(new Guid(DistributedCache.UnpublishedPageCacheRefresherId), x => x.Id, content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// invokes the unpublished page cache refresher
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="content"></param>
|
||||
public static void RemoveUnpublishedPageCache(this DistributedCache dc, params IContent[] content)
|
||||
{
|
||||
dc.Remove(new Guid(DistributedCache.UnpublishedPageCacheRefresherId), x => x.Id, content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// invokes the unpublished page cache refresher to mark all ids for permanent removal
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="contentIds"></param>
|
||||
public static void RemoveUnpublishedCachePermanently(this DistributedCache dc, params int[] contentIds)
|
||||
public static void RemovePageCache(this DistributedCache dc, params int[] contentIds)
|
||||
{
|
||||
dc.RefreshByJson(new Guid(DistributedCache.PageCacheRefresherId),
|
||||
PageCacheRefresher.SerializeToJsonPayload(PageCacheRefresher.OperationType.Deleted, contentIds));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// invokes the unpublished page cache refresher
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="contentIds"></param>
|
||||
public static void RefreshUnpublishedPageCache(this DistributedCache dc, params int[] contentIds)
|
||||
{
|
||||
dc.RefreshByJson(new Guid(DistributedCache.UnpublishedPageCacheRefresherId),
|
||||
UnpublishedPageCacheRefresher.SerializeToJsonPayloadForPermanentDeletion(contentIds));
|
||||
UnpublishedPageCacheRefresher.SerializeToJsonPayload(UnpublishedPageCacheRefresher.OperationType.Saved, contentIds));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// invokes the unpublished page cache refresher
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="contentIds"></param>
|
||||
public static void RemoveUnpublishedPageCache(this DistributedCache dc, params int[] contentIds)
|
||||
{
|
||||
dc.RefreshByJson(new Guid(DistributedCache.UnpublishedPageCacheRefresherId),
|
||||
UnpublishedPageCacheRefresher.SerializeToJsonPayload(UnpublishedPageCacheRefresher.OperationType.Deleted, contentIds));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Web.Script.Serialization;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Sync;
|
||||
using umbraco;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Web.Cache
|
||||
{
|
||||
@@ -15,7 +17,7 @@ namespace Umbraco.Web.Cache
|
||||
/// If Load balancing is enabled (by default disabled, is set in umbracoSettings.config) PageCacheRefresher will be called
|
||||
/// everytime content is added/updated/removed to ensure that the content cache is identical on all load balanced servers
|
||||
/// </remarks>
|
||||
public class PageCacheRefresher : TypedCacheRefresherBase<PageCacheRefresher, IContent>
|
||||
public class PageCacheRefresher : JsonCacheRefresherBase<PageCacheRefresher>
|
||||
{
|
||||
|
||||
protected override PageCacheRefresher Instance
|
||||
@@ -44,6 +46,51 @@ namespace Umbraco.Web.Cache
|
||||
get { return "Page Refresher"; }
|
||||
}
|
||||
|
||||
#region Static helpers
|
||||
|
||||
/// <summary>
|
||||
/// Converts the json to a JsonPayload object
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
/// <returns></returns>
|
||||
internal static JsonPayload[] DeserializeFromJsonPayload(string json)
|
||||
{
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var jsonObject = serializer.Deserialize<JsonPayload[]>(json);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
||||
internal static string SerializeToJsonPayload(OperationType op, params int[] contentIds)
|
||||
{
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var items = contentIds.Select(x => new JsonPayload
|
||||
{
|
||||
Id = x,
|
||||
Operation = op
|
||||
}).ToArray();
|
||||
var json = serializer.Serialize(items);
|
||||
return json;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sub classes
|
||||
|
||||
internal enum OperationType
|
||||
{
|
||||
Deleted,
|
||||
Saved
|
||||
}
|
||||
|
||||
internal class JsonPayload
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public OperationType Operation { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes all nodes in umbraco.
|
||||
/// </summary>
|
||||
@@ -79,22 +126,36 @@ namespace Umbraco.Web.Cache
|
||||
base.Remove(id);
|
||||
}
|
||||
|
||||
public override void Refresh(IContent instance)
|
||||
/// <summary>
|
||||
/// Implement the IJsonCacheRefresher so that we can bulk delete/refresh the cache based on multiple IDs
|
||||
/// </summary>
|
||||
/// <param name="jsonPayload"></param>
|
||||
public override void Refresh(string jsonPayload)
|
||||
{
|
||||
ApplicationContext.Current.ApplicationCache.ClearPartialViewCache();
|
||||
content.Instance.UpdateDocumentCache(new Document(instance));
|
||||
DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer();
|
||||
DistributedCache.Instance.ClearXsltCacheOnCurrentServer();
|
||||
base.Refresh(instance);
|
||||
}
|
||||
var payloads = DeserializeFromJsonPayload(jsonPayload)
|
||||
.Where(x => x.Id > 0)
|
||||
.ToArray();
|
||||
|
||||
if (payloads.Any() == false) return;
|
||||
|
||||
public override void Remove(IContent instance)
|
||||
{
|
||||
ApplicationContext.Current.ApplicationCache.ClearPartialViewCache();
|
||||
content.Instance.ClearDocumentCache(new Document(instance));
|
||||
DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer();
|
||||
DistributedCache.Instance.ClearXsltCacheOnCurrentServer();
|
||||
base.Remove(instance);
|
||||
|
||||
foreach (var payload in payloads)
|
||||
{
|
||||
switch (payload.Operation)
|
||||
{
|
||||
case OperationType.Deleted:
|
||||
content.Instance.ClearDocumentCache(payload.Id);
|
||||
break;
|
||||
case OperationType.Saved:
|
||||
content.Instance.UpdateDocumentCache(payload.Id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
base.Refresh(jsonPayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,11 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
private void RemoveFromCache(int id)
|
||||
{
|
||||
//We'll need to clear all content cache because ITemplate is a property of IContent which
|
||||
// is cached against each intance of IContent :(
|
||||
// TODO: Re-think how we are caching full entities.
|
||||
RuntimeCacheProvider.Current.Clear(typeof(IContent));
|
||||
|
||||
ApplicationContext.Current.ApplicationCache.ClearCacheItem(
|
||||
string.Format("{0}{1}", CacheKeys.TemplateFrontEndCacheKey, id));
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Web.Cache
|
||||
/// <summary>
|
||||
/// A cache refresher used for non-published content, this is primarily to notify Examine indexes to update and to refresh the RuntimeCacheRefresher
|
||||
/// </summary>
|
||||
public sealed class UnpublishedPageCacheRefresher : TypedCacheRefresherBase<UnpublishedPageCacheRefresher, IContent>, IJsonCacheRefresher
|
||||
public sealed class UnpublishedPageCacheRefresher : JsonCacheRefresherBase<UnpublishedPageCacheRefresher>
|
||||
{
|
||||
protected override UnpublishedPageCacheRefresher Instance
|
||||
{
|
||||
@@ -43,13 +43,13 @@ namespace Umbraco.Web.Cache
|
||||
}
|
||||
|
||||
|
||||
internal static string SerializeToJsonPayloadForPermanentDeletion(params int[] contentIds)
|
||||
internal static string SerializeToJsonPayload(OperationType op, params int[] contentIds)
|
||||
{
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var items = contentIds.Select(x => new JsonPayload
|
||||
{
|
||||
Id = x,
|
||||
Operation = OperationType.Deleted
|
||||
Operation = op
|
||||
}).ToArray();
|
||||
var json = serializer.Serialize(items);
|
||||
return json;
|
||||
@@ -61,7 +61,8 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
internal enum OperationType
|
||||
{
|
||||
Deleted
|
||||
Deleted,
|
||||
Saved
|
||||
}
|
||||
|
||||
internal class JsonPayload
|
||||
@@ -90,31 +91,22 @@ namespace Umbraco.Web.Cache
|
||||
base.Remove(id);
|
||||
}
|
||||
|
||||
|
||||
public override void Refresh(IContent instance)
|
||||
{
|
||||
RuntimeCacheProvider.Current.Delete(typeof(IContent), instance.Id);
|
||||
base.Refresh(instance);
|
||||
}
|
||||
|
||||
public override void Remove(IContent instance)
|
||||
{
|
||||
RuntimeCacheProvider.Current.Delete(typeof(IContent), instance.Id);
|
||||
base.Remove(instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implement the IJsonCacheRefresher so that we can bulk delete the cache based on multiple IDs for when the recycle bin is emptied
|
||||
/// Implement the IJsonCacheRefresher so that we can bulk delete/refresh the cache based on multiple IDs
|
||||
/// </summary>
|
||||
/// <param name="jsonPayload"></param>
|
||||
public void Refresh(string jsonPayload)
|
||||
public override void Refresh(string jsonPayload)
|
||||
{
|
||||
foreach (var payload in DeserializeFromJsonPayload(jsonPayload))
|
||||
var payloads = DeserializeFromJsonPayload(jsonPayload)
|
||||
.Where(x => x.Id > 0)
|
||||
.ToArray();
|
||||
|
||||
foreach (var payload in payloads)
|
||||
{
|
||||
RuntimeCacheProvider.Current.Delete(typeof(IContent), payload.Id);
|
||||
}
|
||||
|
||||
OnCacheUpdated(Instance, new CacheRefresherEventArgs(jsonPayload, MessageType.RefreshByJson));
|
||||
base.Refresh(jsonPayload);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -110,6 +110,10 @@ namespace Umbraco.Web.Editors
|
||||
{"manifestAssetList", Url.Action("GetManifestAssetList", "BackOffice")},
|
||||
{"serverVarsJs", Url.Action("Application", "BackOffice")},
|
||||
//API URLs
|
||||
{
|
||||
"segmentDashboardApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<SegmentDashboardController>(
|
||||
controller => controller.GetProviders())
|
||||
},
|
||||
{
|
||||
"embedApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<RteEmbedController>(
|
||||
controller => controller.GetEmbed("",0,0))
|
||||
|
||||
@@ -8,18 +8,23 @@ using System.Net.Http.Formatting;
|
||||
using System.Text;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Services.Description;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Publishing;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.Mapping;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.WebApi.Binders;
|
||||
@@ -284,7 +289,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
//return the updated model
|
||||
var display = Mapper.Map<IContent, ContentItemDisplay>(contentItem.PersistedContent);
|
||||
|
||||
|
||||
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
|
||||
HandleInvalidModelState(display);
|
||||
|
||||
@@ -559,6 +564,24 @@ namespace Umbraco.Web.Editors
|
||||
return content;
|
||||
}
|
||||
|
||||
[EnsureUserPermissionForContent("id", 'C')]
|
||||
public int PostCreateVariant(int id, string key)
|
||||
{
|
||||
var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
|
||||
|
||||
if (foundContent == null)
|
||||
{
|
||||
HandleContentNotFound(id);
|
||||
return int.MinValue;
|
||||
}
|
||||
|
||||
var contentVariant = Services.ContentService.CreateContentVariant(
|
||||
foundContent,
|
||||
key);
|
||||
|
||||
return contentVariant.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the item can be moved/copied to the new location
|
||||
/// </summary>
|
||||
@@ -737,5 +760,10 @@ namespace Umbraco.Web.Editors
|
||||
return allowed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
public class SegmentDashboardController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a json array of provider information
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public JArray GetProviders()
|
||||
{
|
||||
var providers = ContentSegmentProviderResolver.Current.Providers.ToArray();
|
||||
|
||||
var status = ContentSegmentProvidersStatus.GetProviderStatus();
|
||||
|
||||
var result = providers
|
||||
.Select(x => new
|
||||
{
|
||||
typeName = x.GetType().FullName,
|
||||
displayName = x.Name,
|
||||
description = x.Description,
|
||||
variants = x.AssignableContentVariants,
|
||||
variantConfig = x.ReadVariantConfiguration(),
|
||||
asConfigurable = x as ConfigurableSegmentProvider
|
||||
})
|
||||
.Select(x => new
|
||||
{
|
||||
x.typeName,
|
||||
x.displayName,
|
||||
x.description,
|
||||
configurable = x.asConfigurable != null,
|
||||
segmentConfig = x.asConfigurable != null ? x.asConfigurable.ReadSegmentConfiguration() : null,
|
||||
enabled = status.ContainsKey(x.typeName) && status[x.typeName],
|
||||
//SD: I don't feel like creating yet another simple model to send this information in json so I'm just making
|
||||
// an anonymous one.
|
||||
variantConfig = x.variants.Select(vari => new
|
||||
{
|
||||
name = vari.VariantName,
|
||||
key = vari.SegmentMatchKey,
|
||||
enabled = x.variantConfig.ContainsKey(vari.SegmentMatchKey) && x.variantConfig[vari.SegmentMatchKey],
|
||||
asVariant = x.variantConfig.ContainsKey(vari.SegmentMatchKey) && x.variantConfig[vari.SegmentMatchKey]
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
return JArray.FromObject(result);
|
||||
}
|
||||
|
||||
public HttpResponseMessage PostSaveProviderSegmentConfig([FromUri]string typeName, IEnumerable<SegmentProviderMatch> config)
|
||||
{
|
||||
//TODO: We need to validate the config to ensure there are no nulls like null keys
|
||||
|
||||
var providers = ContentSegmentProviderResolver.Current.Providers.ToArray();
|
||||
var provider = providers.FirstOrDefault(x => x.GetType().FullName == typeName) as ConfigurableSegmentProvider;
|
||||
if (provider == null)
|
||||
{
|
||||
return Request.CreateErrorResponse(
|
||||
HttpStatusCode.InternalServerError,
|
||||
"No provider found with name " + typeName + " or the provider type is not configurable");
|
||||
}
|
||||
provider.WriteSegmentConfiguration(config);
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
public HttpResponseMessage PostSaveProviderVariantConfig([FromUri]string typeName, JArray config)
|
||||
{
|
||||
//TODO: We need to validate the config
|
||||
|
||||
var asDictionary = config.ToDictionary(x => (string)x["key"], x => (bool)x["enabled"]);
|
||||
|
||||
var providers = ContentSegmentProviderResolver.Current.Providers.ToArray();
|
||||
var provider = providers.FirstOrDefault(x => x.GetType().FullName == typeName);
|
||||
if (provider == null)
|
||||
{
|
||||
return Request.CreateErrorResponse(
|
||||
HttpStatusCode.InternalServerError,
|
||||
"No provider found with name " + typeName + " or the provider type is not configurable");
|
||||
}
|
||||
provider.WriteVariantConfiguration(asDictionary);
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enabled/disable a provider
|
||||
/// </summary>
|
||||
/// <param name="typeName"></param>
|
||||
/// <returns></returns>
|
||||
public HttpResponseMessage PostToggleProvider([FromUri]string typeName)
|
||||
{
|
||||
var providers = ContentSegmentProviderResolver.Current.Providers.ToArray();
|
||||
var status = ContentSegmentProvidersStatus.GetProviderStatus();
|
||||
|
||||
var provider = providers.FirstOrDefault(x => x.GetType().FullName == typeName);
|
||||
if (provider != null)
|
||||
{
|
||||
if (status.ContainsKey(typeName))
|
||||
{
|
||||
//reverse
|
||||
status[typeName] = (status[typeName] == false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//if it doesn't exist it's not currently enabled
|
||||
status[typeName] = true;
|
||||
}
|
||||
|
||||
ContentSegmentProvidersStatus.SaveProvidersStatus(status);
|
||||
}
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using System.Web.Http.ModelBinding;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Validation;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
@@ -20,6 +21,11 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataContract(Name = "content", Namespace = "")]
|
||||
public class ContentItemDisplay : ListViewAwareContentItemDisplayBase<ContentPropertyDisplay, IContent>
|
||||
{
|
||||
public ContentItemDisplay()
|
||||
{
|
||||
ContentVariants = Enumerable.Empty<ContentVariableSegment>();
|
||||
}
|
||||
|
||||
[DataMember(Name = "publishDate")]
|
||||
public DateTime? PublishDate { get; set; }
|
||||
|
||||
@@ -44,5 +50,25 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataMember(Name = "allowedActions")]
|
||||
public IEnumerable<char> AllowedActions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of the content variant segments allowed/assigned to this content item
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If there are no variants then no drop down is shown
|
||||
/// </remarks>
|
||||
[DataMember(Name = "variants")]
|
||||
public IEnumerable<ContentVariableSegment> ContentVariants { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If this is a variant, the master doc id will not be null
|
||||
/// </summary>
|
||||
[DataMember(Name = "masterDocId")]
|
||||
public int? MasterDocId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If this is a variant, this is it's key
|
||||
/// </summary>
|
||||
[DataMember(Name = "variantKey")]
|
||||
public string VariantKey { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,15 @@ using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using AutoMapper;
|
||||
using Newtonsoft.Json;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Mapping;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
using Umbraco.Web.Trees;
|
||||
using umbraco;
|
||||
using Umbraco.Web.Routing;
|
||||
@@ -52,7 +55,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
expression => expression.MapFrom(content => content.Parent().ContentType.IsContainer))
|
||||
.ForMember(
|
||||
dto => dto.PublishDate,
|
||||
expression => expression.MapFrom(content => GetPublishedDate(content, applicationContext)))
|
||||
expression => expression.MapFrom(content => GetPublishedDate(content, applicationContext.Services.ContentService)))
|
||||
.ForMember(
|
||||
dto => dto.TemplateAlias, expression => expression.MapFrom(content => content.Template.Alias))
|
||||
.ForMember(
|
||||
@@ -69,7 +72,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
.ForMember(display => display.Tabs, expression => expression.ResolveUsing<TabsAndPropertiesResolver>())
|
||||
.ForMember(display => display.AllowedActions, expression => expression.ResolveUsing(
|
||||
new ActionButtonsResolver(new Lazy<IUserService>(() => applicationContext.Services.UserService))))
|
||||
.AfterMap(AfterMap);
|
||||
.AfterMap((content, display) => AfterMap(content, display, applicationContext.Services.ContentService));
|
||||
|
||||
//FROM IContent TO ContentItemBasic<ContentPropertyBasic, IContent>
|
||||
config.CreateMap<IContent, ContentItemBasic<ContentPropertyBasic, IContent>>()
|
||||
@@ -105,7 +108,8 @@ namespace Umbraco.Web.Models.Mapping
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="display"></param>
|
||||
private static void AfterMap(IContent content, ContentItemDisplay display)
|
||||
/// <param name="contentService"></param>
|
||||
private static void AfterMap(IContent content, ContentItemDisplay display, IContentService contentService)
|
||||
{
|
||||
//map the tree node url
|
||||
if (HttpContext.Current != null)
|
||||
@@ -161,17 +165,17 @@ namespace Umbraco.Web.Models.Mapping
|
||||
Value = string.Join(",", display.Urls),
|
||||
View = "urllist" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor
|
||||
});
|
||||
|
||||
MapVariants(content, display, contentService);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published date value for the IContent object
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="applicationContext"></param>
|
||||
/// <param name="contentService"></param>
|
||||
/// <returns></returns>
|
||||
private static DateTime? GetPublishedDate(IContent content, ApplicationContext applicationContext)
|
||||
private static DateTime? GetPublishedDate(IContent content, IContentService contentService)
|
||||
{
|
||||
if (content.Published)
|
||||
{
|
||||
@@ -179,7 +183,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
}
|
||||
if (content.HasPublishedVersion())
|
||||
{
|
||||
var published = applicationContext.Services.ContentService.GetPublishedVersion(content.Id);
|
||||
var published = contentService.GetPublishedVersion(content.Id);
|
||||
return published.UpdateDate;
|
||||
}
|
||||
return null;
|
||||
@@ -240,5 +244,98 @@ namespace Umbraco.Web.Models.Mapping
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: This uses other services which will need to be wired up in order for this mapper to execute, create overloaded ctor's so we ca
|
||||
// test or inject these services.
|
||||
|
||||
private static void MapVariants(IContent content, ContentItemDisplay display, IContentService contentService)
|
||||
{
|
||||
if (content.HasIdentity == false) return;
|
||||
|
||||
//if it's not a variant - go get it's variants
|
||||
if (content.VariantInfo.IsVariant == false)
|
||||
{
|
||||
var variantDef = contentService.GetByIds(content.VariantInfo.VariantIds);
|
||||
|
||||
//if it's not a variant, then it's a master-doc so go lookup the possible variant types it can have
|
||||
var segmentProviderStatus = ContentSegmentProvidersStatus.GetProviderStatus();
|
||||
|
||||
var assignableVariants = ContentSegmentProviderResolver.Current.GetAssignableVariants(segmentProviderStatus);
|
||||
|
||||
//These are the assignable variants based on the installed providers (statically advertised variants)
|
||||
// that are enabled via the back office. If they are not enabled, they will not show up.
|
||||
var assignableSegments = assignableVariants
|
||||
.Select(variantAttribute => new
|
||||
{
|
||||
variantAttribute,
|
||||
assigned = variantDef.FirstOrDefault(k => k.VariantInfo.Key == variantAttribute.SegmentMatchKey)
|
||||
})
|
||||
.Select(x => x.assigned == null
|
||||
? new ContentVariableSegment(x.variantAttribute.VariantName, x.variantAttribute.SegmentMatchKey, false)
|
||||
: new ContentVariableSegment(x.variantAttribute.VariantName, x.variantAttribute.SegmentMatchKey, false, x.assigned.Id, x.assigned.Trashed, x.assigned.UpdateDate));
|
||||
|
||||
var assignedLanguages = GetAssignedLanguageVariants(display);
|
||||
|
||||
var languageSegments = assignedLanguages
|
||||
.Select(x => new { lang = x, assigned = variantDef.FirstOrDefault(k => k.VariantInfo.Key == x) })
|
||||
.Select(x => x.assigned == null
|
||||
? new ContentVariableSegment(x.lang, true)
|
||||
: new ContentVariableSegment(x.lang, true, x.assigned.Id, x.assigned.Trashed, x.assigned.UpdateDate));
|
||||
|
||||
//assign the variants, NOTE: languages always take precedence if there is overlap
|
||||
display.ContentVariants = languageSegments.Union(assignableSegments);
|
||||
}
|
||||
else
|
||||
{
|
||||
display.MasterDocId = content.VariantInfo.MasterDocId;
|
||||
display.VariantKey = content.VariantInfo.Key;
|
||||
|
||||
//We want to change the URL property because it shouldn't show urls if it's a variant, the URL will be specific
|
||||
// to the master doc - if it is NOT a language variant.
|
||||
//For language variants the URL should only reflect the one assigned by domain.
|
||||
|
||||
var genericTab = display.Tabs.Single(x => x.Id == 0);
|
||||
var urlProp = genericTab.Properties.Single(x => x.Alias == string.Format("{0}urls", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
|
||||
var assignedLanguages = GetAssignedLanguageVariants(display);
|
||||
if (assignedLanguages.Contains(content.VariantInfo.Key) == false)
|
||||
{
|
||||
//it's not a language, so remove the url
|
||||
|
||||
//TODO: show a message? or just remove the prop?
|
||||
//var labelEditor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View;
|
||||
genericTab.Properties = genericTab.Properties.Except(new[] {urlProp});
|
||||
//urlProp.Value = "The URL is the same as the master doc, custom variants do not have different URLs";
|
||||
//urlProp.Label = "";
|
||||
//urlProp.View = labelEditor;
|
||||
}
|
||||
else
|
||||
{
|
||||
//it is a language so only show that specific domain URL.
|
||||
//TODO: How??? There doesn't seem to be an easy way to do this need to ask Stephen
|
||||
//urlProp.Value = content.GetContentUrls();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetAssignedLanguageVariants(ContentItemDisplay display)
|
||||
{
|
||||
//These are the lanuages assigned to this node (i.e. based on domains assigned to this node or ancestor nodes)
|
||||
var allDomains = DomainHelper.GetAllDomains(false);
|
||||
|
||||
//now get the ones assigned within the path
|
||||
var splitPath = display.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
|
||||
if (display.MasterDocId.HasValue)
|
||||
{
|
||||
//we need to add the master doc id to the path, since that is really where the languages are assigned
|
||||
splitPath.Insert(1, display.MasterDocId.Value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
var assignedDomains = allDomains.Where(x => splitPath.Contains(x.RootNodeId.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
return assignedDomains.Select(x => x.Language.CultureAlias);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// A definition of a segment that can be used to create content variants
|
||||
/// </summary>
|
||||
[DataContract(Name = "segment", Namespace = "")]
|
||||
public class ContentVariableSegment
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor for a variant that does not exist yet
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="isLanguage"></param>
|
||||
public ContentVariableSegment(string key, bool isLanguage)
|
||||
{
|
||||
Name = key;
|
||||
Key = key;
|
||||
IsLanguage = isLanguage;
|
||||
Exists = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// COnstructor for a variant that exists
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="isLanguage"></param>
|
||||
/// <param name="variantId"></param>
|
||||
/// <param name="isTrashed"></param>
|
||||
/// <param name="lastUpdated"></param>
|
||||
public ContentVariableSegment(string key, bool isLanguage, int variantId, bool isTrashed, DateTime lastUpdated)
|
||||
{
|
||||
Name = key;
|
||||
Key = key;
|
||||
IsLanguage = isLanguage;
|
||||
Exists = true;
|
||||
VariantId = variantId;
|
||||
IsTrashed = isTrashed;
|
||||
LastUpdated = lastUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a variant that does not exist yet
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="isLanguage"></param>
|
||||
public ContentVariableSegment(string name, string key, bool isLanguage)
|
||||
{
|
||||
Name = name;
|
||||
Key = key;
|
||||
IsLanguage = isLanguage;
|
||||
Exists = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// COnstructor for a variant that exists
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="isLanguage"></param>
|
||||
/// <param name="variantId"></param>
|
||||
/// <param name="isTrashed"></param>
|
||||
/// <param name="lastUpdated"></param>
|
||||
public ContentVariableSegment(string name, string key, bool isLanguage, int variantId, bool isTrashed, DateTime lastUpdated)
|
||||
{
|
||||
Name = name;
|
||||
Key = key;
|
||||
IsLanguage = isLanguage;
|
||||
Exists = true;
|
||||
VariantId = variantId;
|
||||
IsTrashed = isTrashed;
|
||||
LastUpdated = lastUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The friendly name of the segment
|
||||
/// </summary>
|
||||
[DataMember(Name = "name")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The segment Key associated with the content variant
|
||||
/// </summary>
|
||||
[DataMember(Name = "key")]
|
||||
public string Key { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the key for this segment is based on a language
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In the case that it is based on a language, the key will be the ISO lang code like en-US
|
||||
/// </remarks>
|
||||
[DataMember(Name = "isLanguage")]
|
||||
public bool IsLanguage { get; private set; }
|
||||
|
||||
[DataMember(Name = "lastUpdated")]
|
||||
public DateTime? LastUpdated { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not a content variant current exists for this segment for a content item
|
||||
/// </summary>
|
||||
[DataMember(Name = "exists")]
|
||||
public bool Exists { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The id of the variant if it exists
|
||||
/// </summary>
|
||||
[DataMember(Name = "id")]
|
||||
public int VariantId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the variant is in the trash or not
|
||||
/// </summary>
|
||||
[DataMember(Name = "isTrashed")]
|
||||
public bool IsTrashed { get; private set; }
|
||||
|
||||
protected bool Equals(ContentVariableSegment other)
|
||||
{
|
||||
return string.Equals(Key, other.Key);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((ContentVariableSegment) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Key.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Web.Models.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an assigned segment in a request
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The serialization names are only one letter - this is intentional to keep the cookie size small
|
||||
/// </remarks>
|
||||
[DataContract(Name = "s", Namespace = "")]
|
||||
public class Segment
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor, required for deserialization
|
||||
/// </summary>
|
||||
public Segment()
|
||||
{
|
||||
}
|
||||
|
||||
public Segment(string key, object value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public Segment(string key, object value, bool persist)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
Persist = persist;
|
||||
}
|
||||
|
||||
internal Segment(string key, object value, bool persist, int? slidingDays)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
Persist = persist;
|
||||
SlidingDays = slidingDays;
|
||||
}
|
||||
|
||||
internal Segment(string key, object value, bool persist, DateTime? absoluteExpiry)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
Persist = persist;
|
||||
AbsoluteExpiry = absoluteExpiry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the segment
|
||||
/// </summary>
|
||||
[DataMember(Name = "k", IsRequired = true)]
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value of the segment
|
||||
/// </summary>
|
||||
[DataMember(Name = "v")]
|
||||
public object Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this segment is to be persisted (default is false)
|
||||
/// </summary>
|
||||
[DataMember(Name = "p")]
|
||||
public bool Persist { get; set; }
|
||||
|
||||
//TODO: We should make use of these expiry settings!
|
||||
|
||||
/// <summary>
|
||||
/// Defines the sliding expiration date in days from now for this particular segment
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
[IgnoreDataMember]
|
||||
internal int? SlidingDays { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines the absolute expiration date for this particular segment
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
[IgnoreDataMember]
|
||||
internal DateTime? AbsoluteExpiry { get; private set; }
|
||||
|
||||
protected bool Equals(Segment other)
|
||||
{
|
||||
return string.Equals(Key, other.Key);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((Segment) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Key.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Umbraco.Web.Models.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// A keyed collection of segments
|
||||
/// </summary>
|
||||
public class SegmentCollection : KeyedCollection<string, Segment>
|
||||
{
|
||||
public SegmentCollection(IEnumerable<Segment> segments)
|
||||
{
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
//last one in wins
|
||||
if (Contains(segment.Key))
|
||||
{
|
||||
Remove(segment.Key);
|
||||
}
|
||||
|
||||
Add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddNew(Segment segment)
|
||||
{
|
||||
//last one in wins
|
||||
if (Contains(segment.Key))
|
||||
{
|
||||
Remove(segment.Key);
|
||||
}
|
||||
|
||||
Add(segment);
|
||||
}
|
||||
|
||||
protected override string GetKeyForItem(Segment item)
|
||||
{
|
||||
return item.Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// Used as a configuration item for a configurable segment provider
|
||||
/// </summary>
|
||||
[DataContract(Name = "segment", Namespace = "")]
|
||||
public class SegmentProviderMatch
|
||||
{
|
||||
/// <summary>
|
||||
/// The Segment Key to add to the request
|
||||
/// </summary>
|
||||
[DataMember(Name = "key")]
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The segment Value to add to the request for this match's Key
|
||||
/// </summary>
|
||||
[DataMember(Name = "value")]
|
||||
public string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The configured expression to match against the advertised value of a configurable segment provider
|
||||
/// </summary>
|
||||
[DataMember(Name = "matchExpression")]
|
||||
public string MatchExpression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A flag of whether or not to persist the segment match to the user's cookies (and to the member profile if they are logged in)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This might be useful if you want to track a segment from a provider, perhaps if it's a referral provider a segment might be set
|
||||
/// when coming from mysite.com and you want to know about that later on when the segment no longer exists in the request.
|
||||
/// </remarks>
|
||||
[DataMember(Name = "persist")]
|
||||
public bool Persist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A flag to determine if this segment is allowed to be a variant (advertised)
|
||||
/// </summary>
|
||||
[DataMember(Name = "asVariant")]
|
||||
public bool AllowedAsVariant { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Xml;
|
||||
using Umbraco.Web.Routing;
|
||||
@@ -187,6 +188,11 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
public string ChildDocumentByUrlNameVar { get; private set; }
|
||||
public string RootDocumentWithLowestSortOrder { get; private set; }
|
||||
|
||||
//xpath statements to take into account variants
|
||||
public string RootDocumentsAndVariants { get; private set; }
|
||||
public string ChildDocumentOrVariantByUrlNameVar { get; private set; }
|
||||
public string ChildDocumentOrVariantByUrlName { get; private set; }
|
||||
|
||||
public XPathStringsDefinition(int version)
|
||||
{
|
||||
Version = version;
|
||||
@@ -195,20 +201,41 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
// legacy XML schema
|
||||
case 0:
|
||||
RootDocuments = "/root/node";
|
||||
DescendantDocumentById = "//node [@id={0}]";
|
||||
ChildDocumentByUrlName = "/node [@urlName='{0}']";
|
||||
ChildDocumentByUrlNameVar = "/node [@urlName=${0}]";
|
||||
RootDocumentWithLowestSortOrder = "/root/node [not(@sortOrder > ../node/@sortOrder)][1]";
|
||||
throw new NotSupportedException("The legacy XML schema is no longer supported");
|
||||
//RootDocuments = "/root/node";
|
||||
//DescendantDocumentById = "//node [@id={0}]";
|
||||
//ChildDocumentByUrlName = "/node [@urlName='{0}']";
|
||||
//ChildDocumentByUrlNameVar = "/node [@urlName=${0}]";
|
||||
//RootDocumentWithLowestSortOrder = "/root/node [not(@sortOrder > ../node/@sortOrder)][1]";
|
||||
break;
|
||||
|
||||
// default XML schema as of 4.10
|
||||
case 1:
|
||||
RootDocuments = "/root/* [@isDoc]";
|
||||
DescendantDocumentById = "//* [@isDoc and @id={0}]";
|
||||
ChildDocumentByUrlName = "/* [@isDoc and @urlName='{0}']";
|
||||
ChildDocumentByUrlNameVar = "/* [@isDoc and @urlName=${0}]";
|
||||
RootDocumentWithLowestSortOrder = "/root/* [@isDoc and not(@sortOrder > ../* [@isDoc]/@sortOrder)][1]";
|
||||
|
||||
//variants have isDoc='variant' so we don't want those
|
||||
RootDocuments = "/root/* [@isDoc='']";
|
||||
|
||||
//master + variants both have isDoc
|
||||
RootDocumentsAndVariants = "/root/* [@isDoc]";
|
||||
|
||||
//variants have isDoc='variant' so we don't want those
|
||||
DescendantDocumentById = "//* [@isDoc='' and @id={0}]";
|
||||
|
||||
//variants have isDoc='variant' so we don't want those
|
||||
ChildDocumentByUrlName = "/* [@isDoc='' and @urlName='{0}']";
|
||||
|
||||
//master + variants both have isDoc
|
||||
ChildDocumentOrVariantByUrlName = "/* [@isDoc and @urlName='{0}']";
|
||||
|
||||
//variants have isDoc='variant' so we don't want those
|
||||
ChildDocumentByUrlNameVar = "/* [@isDoc='' and @urlName=${0}]";
|
||||
|
||||
//master + variants both have isDoc
|
||||
ChildDocumentOrVariantByUrlNameVar = "/* [@isDoc and @urlName=${0}]";
|
||||
|
||||
//variants have isDoc='variant' so we don't want those
|
||||
RootDocumentWithLowestSortOrder = "/root/* [@isDoc='' and not(@sortOrder > ../* [@isDoc='']/@sortOrder)][1]";
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -238,17 +265,17 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
#region Converters
|
||||
|
||||
private static IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing)
|
||||
private static IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing, VariantInfo variantInfo)
|
||||
{
|
||||
return xmlNode == null
|
||||
? null
|
||||
: (new XmlPublishedContent(xmlNode, isPreviewing)).CreateModel();
|
||||
? null
|
||||
: (new XmlPublishedContent(xmlNode, isPreviewing, variantInfo)).CreateModel();
|
||||
}
|
||||
|
||||
private static IEnumerable<IPublishedContent> ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing)
|
||||
private static IEnumerable<IPublishedContent> ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing, VariantInfo variantInfo)
|
||||
{
|
||||
return xmlNodes.Cast<XmlNode>()
|
||||
.Select(xmlNode => (new XmlPublishedContent(xmlNode, isPreviewing)).CreateModel());
|
||||
.Select(xmlNode => (new XmlPublishedContent(xmlNode, isPreviewing, variantInfo)).CreateModel());
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -257,12 +284,20 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int nodeId)
|
||||
{
|
||||
return ConvertToDocument(GetXml(umbracoContext, preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)), preview);
|
||||
return ConvertToDocument(
|
||||
GetXml(umbracoContext, preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)),
|
||||
preview,
|
||||
//TODO: Do something about this :(
|
||||
umbracoContext.PublishedContentRequest == null ? new VariantInfo() : umbracoContext.PublishedContentRequest.VariantInfo);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IPublishedContent> GetAtRoot(UmbracoContext umbracoContext, bool preview)
|
||||
{
|
||||
return ConvertToDocuments(GetXml(umbracoContext, preview).SelectNodes(XPathStrings.RootDocuments), preview);
|
||||
return ConvertToDocuments(
|
||||
GetXml(umbracoContext, preview).SelectNodes(XPathStrings.RootDocuments),
|
||||
preview,
|
||||
//TODO: Do something about this :(
|
||||
umbracoContext.PublishedContentRequest == null ? new VariantInfo() : umbracoContext.PublishedContentRequest.VariantInfo);
|
||||
}
|
||||
|
||||
public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, string xpath, params XPathVariable[] vars)
|
||||
@@ -274,7 +309,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
var node = vars == null
|
||||
? xml.SelectSingleNode(xpath)
|
||||
: xml.SelectSingleNode(xpath, vars);
|
||||
return ConvertToDocument(node, preview);
|
||||
return ConvertToDocument(node, preview,
|
||||
//TODO: Do something about this :(
|
||||
umbracoContext.PublishedContentRequest == null ? new VariantInfo() : umbracoContext.PublishedContentRequest.VariantInfo);
|
||||
}
|
||||
|
||||
public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, params XPathVariable[] vars)
|
||||
@@ -285,7 +322,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
var node = vars == null
|
||||
? xml.SelectSingleNode(xpath)
|
||||
: xml.SelectSingleNode(xpath, vars);
|
||||
return ConvertToDocument(node, preview);
|
||||
return ConvertToDocument(node, preview,
|
||||
//TODO: Do something about this :(
|
||||
umbracoContext.PublishedContentRequest == null ? new VariantInfo() : umbracoContext.PublishedContentRequest.VariantInfo);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IPublishedContent> GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, params XPathVariable[] vars)
|
||||
@@ -297,7 +336,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
var nodes = vars == null
|
||||
? xml.SelectNodes(xpath)
|
||||
: xml.SelectNodes(xpath, vars);
|
||||
return ConvertToDocuments(nodes, preview);
|
||||
return ConvertToDocuments(nodes, preview,
|
||||
//TODO: Do something about this :(
|
||||
umbracoContext.PublishedContentRequest == null ? new VariantInfo() : umbracoContext.PublishedContentRequest.VariantInfo);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IPublishedContent> GetByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, params XPathVariable[] vars)
|
||||
@@ -308,7 +349,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
var nodes = vars == null
|
||||
? xml.SelectNodes(xpath)
|
||||
: xml.SelectNodes(xpath, vars);
|
||||
return ConvertToDocuments(nodes, preview);
|
||||
return ConvertToDocuments(nodes, preview,
|
||||
//TODO: Do something about this :(
|
||||
umbracoContext.PublishedContentRequest == null ? new VariantInfo() : umbracoContext.PublishedContentRequest.VariantInfo);
|
||||
}
|
||||
|
||||
public virtual bool HasContent(UmbracoContext umbracoContext, bool preview)
|
||||
@@ -383,6 +426,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
static readonly char[] SlashChar = new[] { '/' };
|
||||
|
||||
/// <summary>
|
||||
/// Creates an xpath query to lookup a node by route
|
||||
/// </summary>
|
||||
/// <param name="startNodeId"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="hideTopLevelNodeFromPath"></param>
|
||||
/// <param name="vars"></param>
|
||||
/// <returns></returns>
|
||||
protected string CreateXpathQuery(int startNodeId, string path, bool hideTopLevelNodeFromPath, out IEnumerable<XPathVariable> vars)
|
||||
{
|
||||
string xpath;
|
||||
@@ -407,7 +458,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
// read http://stackoverflow.com/questions/1128745/how-can-i-use-xpath-to-find-the-minimum-value-of-an-attribute-in-a-set-of-elemen
|
||||
|
||||
// so that one does not work, because min(@sortOrder) maybe 1
|
||||
// xpath = "/root/*[@isDoc and @sortOrder='0']";
|
||||
// xpath = "/root/*[@isDoc='' and @sortOrder='0']";
|
||||
|
||||
// and we can't use min() because that's XPath 2.0
|
||||
// that one works
|
||||
@@ -424,10 +475,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
if (startNodeId == 0)
|
||||
{
|
||||
if (hideTopLevelNodeFromPath)
|
||||
xpathBuilder.Append(XPathStrings.RootDocuments); // first node is not in the url
|
||||
else
|
||||
xpathBuilder.Append(XPathStringsDefinition.Root);
|
||||
if (hideTopLevelNodeFromPath)
|
||||
{
|
||||
xpathBuilder.Append(XPathStrings.RootDocumentsAndVariants); // first node is not in the url
|
||||
}
|
||||
else
|
||||
{
|
||||
xpathBuilder.Append(XPathStringsDefinition.Root);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -444,11 +499,11 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
varsList = varsList ?? new List<XPathVariable>();
|
||||
var varName = string.Format("var{0}", partsIndex);
|
||||
varsList.Add(new XPathVariable(varName, part));
|
||||
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlNameVar, varName);
|
||||
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentOrVariantByUrlNameVar, varName);
|
||||
}
|
||||
else
|
||||
{
|
||||
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlName, part);
|
||||
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentOrVariantByUrlName, part);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Xml.XPath;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web.Models;
|
||||
|
||||
@@ -21,33 +22,39 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
[XmlType(Namespace = "http://umbraco.org/webservices/")]
|
||||
internal class XmlPublishedContent : PublishedContentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <c>XmlPublishedContent</c> class with an Xml node.
|
||||
/// </summary>
|
||||
/// <param name="xmlNode">The Xml node.</param>
|
||||
/// <param name="isPreviewing">A value indicating whether the published content is being previewed.</param>
|
||||
public XmlPublishedContent(XmlNode xmlNode, bool isPreviewing)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <c>XmlPublishedContent</c> class with an Xml node.
|
||||
/// </summary>
|
||||
/// <param name="xmlNode">The Xml node.</param>
|
||||
/// <param name="isPreviewing">A value indicating whether the published content is being previewed.</param>
|
||||
/// <param name="variantInfo"></param>
|
||||
public XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, VariantInfo variantInfo)
|
||||
{
|
||||
_xmlNode = xmlNode;
|
||||
if (variantInfo == null) throw new ArgumentNullException("variantInfo");
|
||||
_xmlNode = xmlNode;
|
||||
_isPreviewing = isPreviewing;
|
||||
InitializeStructure();
|
||||
_variantInfo = variantInfo;
|
||||
InitializeStructure();
|
||||
Initialize();
|
||||
InitializeChildren();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <c>XmlPublishedContent</c> class with an Xml node,
|
||||
/// and a value indicating whether to lazy-initialize the instance.
|
||||
/// </summary>
|
||||
/// <param name="xmlNode">The Xml node.</param>
|
||||
/// <param name="isPreviewing">A value indicating whether the published content is being previewed.</param>
|
||||
/// <param name="lazyInitialize">A value indicating whether to lazy-initialize the instance.</param>
|
||||
/// <remarks>Lazy-initializationg is NOT thread-safe.</remarks>
|
||||
internal XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, bool lazyInitialize)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <c>XmlPublishedContent</c> class with an Xml node,
|
||||
/// and a value indicating whether to lazy-initialize the instance.
|
||||
/// </summary>
|
||||
/// <param name="xmlNode">The Xml node.</param>
|
||||
/// <param name="isPreviewing">A value indicating whether the published content is being previewed.</param>
|
||||
/// <param name="lazyInitialize">A value indicating whether to lazy-initialize the instance.</param>
|
||||
/// <param name="variantInfo"></param>
|
||||
/// <remarks>Lazy-initializationg is NOT thread-safe.</remarks>
|
||||
internal XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, bool lazyInitialize, VariantInfo variantInfo)
|
||||
{
|
||||
_xmlNode = xmlNode;
|
||||
if (variantInfo == null) throw new ArgumentNullException("variantInfo");
|
||||
_xmlNode = xmlNode;
|
||||
_isPreviewing = isPreviewing;
|
||||
InitializeStructure();
|
||||
_variantInfo = variantInfo;
|
||||
InitializeStructure();
|
||||
if (lazyInitialize == false)
|
||||
{
|
||||
Initialize();
|
||||
@@ -82,6 +89,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
private int _level;
|
||||
private bool _isDraft;
|
||||
private readonly bool _isPreviewing;
|
||||
private readonly VariantInfo _variantInfo;
|
||||
private PublishedContentType _contentType;
|
||||
|
||||
public override IEnumerable<IPublishedContent> Children
|
||||
@@ -337,8 +345,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
var parent = _xmlNode == null ? null : _xmlNode.ParentNode;
|
||||
if (parent == null) return;
|
||||
|
||||
if (parent.Name == "node" || (parent.Attributes != null && parent.Attributes.GetNamedItem("isDoc") != null))
|
||||
_parent = (new XmlPublishedContent(parent, _isPreviewing, true)).CreateModel();
|
||||
if (parent.LocalName == "node" || (parent.Attributes != null && parent.Attributes.GetNamedItem("isDoc") != null))
|
||||
_parent = (new XmlPublishedContent(parent, _isPreviewing, true, _variantInfo)).CreateModel();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
@@ -381,7 +389,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
}
|
||||
else
|
||||
{
|
||||
_docTypeAlias = _xmlNode.Name;
|
||||
_docTypeAlias = _xmlNode.LocalName;
|
||||
}
|
||||
|
||||
if (_xmlNode.Attributes.GetNamedItem("nodeType") != null)
|
||||
@@ -412,7 +420,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
var alias = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema
|
||||
? n.Attributes.GetNamedItem("alias").Value
|
||||
: n.Name;
|
||||
: n.LocalName;
|
||||
propertyNodes[alias.ToLowerInvariant()] = n;
|
||||
}
|
||||
|
||||
@@ -432,17 +440,69 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
if (_xmlNode == null) return;
|
||||
|
||||
var workingNode = _xmlNode;
|
||||
|
||||
//if we're current using a variant, we need to go lookup the master since that is
|
||||
// how we navigate the tree - always via the master
|
||||
if (_variantInfo.IsVariant)
|
||||
{
|
||||
var masterDocId = _xmlNode.AttributeValue<int>("masterDocId");
|
||||
//get the master, then children
|
||||
if (_xmlNode.ParentNode != null)
|
||||
{
|
||||
//TODO: There's probably a faster way with the old xml api to do this
|
||||
var master = _xmlNode.ParentNode.ChildNodes.OfType<XmlElement>().FirstOrDefault(x => x.AttributeValue<int>("id") == masterDocId);
|
||||
if (master != null)
|
||||
{
|
||||
workingNode = master;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load children
|
||||
var childXPath = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "node" : "* [@isDoc]";
|
||||
var nav = _xmlNode.CreateNavigator();
|
||||
//variants have isDoc='variant' so we don't want those
|
||||
var childXPath = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "node" : "* [@isDoc='']";
|
||||
var nav = workingNode.CreateNavigator();
|
||||
var expr = nav.Compile(childXPath);
|
||||
expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number);
|
||||
var iterator = nav.Select(expr);
|
||||
while (iterator.MoveNext())
|
||||
_children.Add(
|
||||
(new XmlPublishedContent(((IHasXmlNode)iterator.Current).GetNode(), _isPreviewing, true)).CreateModel());
|
||||
|
||||
// warn: this is not thread-safe
|
||||
while (iterator.MoveNext())
|
||||
{
|
||||
var xmlChild = ((IHasXmlNode) iterator.Current).GetNode();
|
||||
|
||||
var useVariant = false;
|
||||
|
||||
//determine which child to add based on the current VariantInfo
|
||||
if (_variantInfo.IsVariant)
|
||||
{
|
||||
//TODO: There's probably a faster way with the old xml api to do this
|
||||
|
||||
//find a variant of the current child for the current variant key
|
||||
var variant = workingNode.ChildNodes.OfType<XmlElement>()
|
||||
.FirstOrDefault(x =>
|
||||
//find all
|
||||
x.AttributeValue<int>("masterDocId") == xmlChild.AttributeValue<int>("id")
|
||||
&& x.AttributeValue<string>("variantKey") == _variantInfo.Key);
|
||||
|
||||
if (variant != null)
|
||||
{
|
||||
//we found a variant!
|
||||
_children.Add(
|
||||
(new XmlPublishedContent(variant, _isPreviewing, true, _variantInfo)).CreateModel());
|
||||
useVariant = true;
|
||||
}
|
||||
}
|
||||
|
||||
//no variant used, use the normal child
|
||||
if (useVariant == false)
|
||||
{
|
||||
_children.Add(
|
||||
(new XmlPublishedContent(xmlChild, _isPreviewing, true, _variantInfo)).CreateModel());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// warn: this is not thread-safe
|
||||
_childrenInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
@@ -19,14 +22,18 @@ namespace Umbraco.Web.Routing
|
||||
/// <returns>A value indicating whether an Umbraco document was found and assigned.</returns>
|
||||
public virtual bool TryFindContent(PublishedContentRequest docRequest)
|
||||
{
|
||||
string route;
|
||||
if (docRequest.HasDomain)
|
||||
route = docRequest.Domain.RootNodeId.ToString() + DomainHelper.PathRelativeToDomain(docRequest.DomainUri, docRequest.Uri.GetAbsolutePathDecoded());
|
||||
else
|
||||
route = docRequest.Uri.GetAbsolutePathDecoded();
|
||||
|
||||
var node = FindContent(docRequest, route);
|
||||
return node != null;
|
||||
if (docRequest.HasDomain)
|
||||
{
|
||||
var route = docRequest.Domain.RootNodeId + DomainHelper.PathRelativeToDomain(docRequest.DomainUri, docRequest.Uri.GetAbsolutePathDecoded());
|
||||
var node = FindContent(docRequest, route);
|
||||
return node != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var route = docRequest.Uri.GetAbsolutePathDecoded();
|
||||
var node = FindContent(docRequest, route);
|
||||
return node != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,10 +46,89 @@ namespace Umbraco.Web.Routing
|
||||
{
|
||||
LogHelper.Debug<ContentFinderByNiceUrl>("Test route \"{0}\"", () => route);
|
||||
|
||||
|
||||
var node = docreq.RoutingContext.UmbracoContext.ContentCache.GetByRoute(route);
|
||||
if (node != null)
|
||||
{
|
||||
docreq.PublishedContent = node;
|
||||
var hasVariant = false;
|
||||
|
||||
//so we have a node but we need to check if a domain is assigned, if one is then we might have a variant for this
|
||||
// particular language, so let's check
|
||||
if (docreq.HasDomain)
|
||||
{
|
||||
var variant = docreq.RoutingContext.UmbracoContext.ContentCache.GetSingleByXPath(
|
||||
string.Format("/root//* [@masterDocId='{0}' and @variantKey='{1}']", node.Id, docreq.Domain.Language.CultureAlias));
|
||||
|
||||
//if there is no variant found, then continue using the master doc content
|
||||
|
||||
if (variant != null)
|
||||
{
|
||||
//TODO: If content is found, but it has variants of the language assigned to the domain found here do we
|
||||
// match against the variant? Or do we match against the master? Or do we 404 ?
|
||||
|
||||
//there is a variant for this culture for this domain so we will use that
|
||||
node = variant;
|
||||
|
||||
hasVariant = true;
|
||||
|
||||
//Set the pcr val
|
||||
docreq.SetVariantInfo(
|
||||
node,
|
||||
new VariantInfo(node.Id, docreq.Domain.Language.CultureAlias));
|
||||
}
|
||||
}
|
||||
|
||||
var reqSegments = docreq.RoutingContext.UmbracoContext.RequestSegments;
|
||||
|
||||
//if no variant was assigned above, then we can check if there's any segment matches and assign a
|
||||
// custom variant if one is found
|
||||
// TODO: Do we want to allow this logic to execute when a Domain is detected?
|
||||
|
||||
if (hasVariant == false && reqSegments.AssignedSegments.Any())
|
||||
{
|
||||
var segmentProviderStatus = ContentSegmentProvidersStatus.GetProviderStatus();
|
||||
var assignableVariants = ContentSegmentProviderResolver.Current.GetAssignableVariants(segmentProviderStatus);
|
||||
|
||||
//get all possible variants for this request if the keys exist
|
||||
var allMatchedVariantsByKey = assignableVariants.Where(x => reqSegments.RequestIs(x.SegmentMatchKey));
|
||||
|
||||
var matchedVariant = allMatchedVariantsByKey.FirstOrDefault(x =>
|
||||
reqSegments.RequestIs(x.SegmentMatchKey)
|
||||
|| reqSegments.RequestEquals(x.SegmentMatchKey, x.SegmentMatchValue));
|
||||
|
||||
if (matchedVariant != null)
|
||||
{
|
||||
//an advertised key is matched, lets see if there's a match (either on boolean or value)
|
||||
var isMatch = reqSegments.RequestIs(matchedVariant.SegmentMatchKey)
|
||||
|| reqSegments.RequestEquals(matchedVariant.SegmentMatchKey, matchedVariant.SegmentMatchValue);
|
||||
|
||||
if (isMatch)
|
||||
{
|
||||
//if the request has a matched segment then let's try to lookup the variant
|
||||
var variant = docreq.RoutingContext.UmbracoContext.ContentCache.GetSingleByXPath(
|
||||
string.Format("/root//* [@masterDocId='{0}' and @variantKey='{1}']", node.Id, matchedVariant.SegmentMatchKey));
|
||||
|
||||
//assign the variant if there is one
|
||||
if (variant != null)
|
||||
{
|
||||
node = variant;
|
||||
hasVariant = true;
|
||||
|
||||
//Set the pcr val
|
||||
docreq.SetVariantInfo(
|
||||
node,
|
||||
new VariantInfo(node.Id, matchedVariant.SegmentMatchKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasVariant == false)
|
||||
{
|
||||
//assign the content as per normal
|
||||
docreq.PublishedContent = node;
|
||||
}
|
||||
|
||||
LogHelper.Debug<ContentFinderByNiceUrl>("Got content, id={0}", () => node.Id);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -88,6 +88,10 @@ namespace Umbraco.Web.Routing
|
||||
{
|
||||
Version = version;
|
||||
|
||||
//TODO: Need to figure out where variants fit in here, probably in most cases below
|
||||
// we'd want to do isDoc='' so that we don't match variants. I have done this for now
|
||||
// since we'd rather not match variants than do match them.
|
||||
|
||||
switch (version)
|
||||
{
|
||||
// legacy XML schema
|
||||
@@ -101,8 +105,10 @@ namespace Umbraco.Web.Routing
|
||||
|
||||
// default XML schema as of 4.10
|
||||
case 1:
|
||||
DescendantDocumentById = "//* [@isDoc and @id={0}]";
|
||||
DescendantDocumentByAlias = "//* [@isDoc and ("
|
||||
|
||||
//variants have isDoc='variant' so we don't want those
|
||||
DescendantDocumentById = "//* [@isDoc='' and @id={0}]";
|
||||
DescendantDocumentByAlias = "//* [@isDoc='' and ("
|
||||
+ "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')"
|
||||
+ " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')"
|
||||
+ ")]";
|
||||
|
||||
@@ -6,6 +6,8 @@ using Umbraco.Core.Models;
|
||||
|
||||
using umbraco;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using Umbraco.Core.Models.ContentVariations;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using RenderingEngine = Umbraco.Core.RenderingEngine;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
@@ -47,6 +49,7 @@ namespace Umbraco.Web.Routing
|
||||
_engine = new PublishedContentRequestEngine(this);
|
||||
|
||||
RenderingEngine = RenderingEngine.Unknown;
|
||||
VariantInfo = new VariantInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -411,6 +414,36 @@ namespace Umbraco.Web.Routing
|
||||
set { _umbracoPage = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns the variant info to the request and re-get's the content item from cache so that it
|
||||
/// is initialized with the correct variant info taken from this PCR
|
||||
/// </summary>
|
||||
/// <param name="current"></param>
|
||||
/// <param name="variantInfo"></param>
|
||||
internal void SetVariantInfo(IPublishedContent current, VariantInfo variantInfo)
|
||||
{
|
||||
//TODO: The way this works is real dodgy!
|
||||
|
||||
if (current == null) throw new ArgumentNullException("current");
|
||||
if (variantInfo == null) throw new ArgumentNullException("variantInfo");
|
||||
|
||||
//set the info on the PCR
|
||||
VariantInfo = variantInfo;
|
||||
|
||||
//re-get the item now that the variant info is set
|
||||
var reGet = RoutingContext.UmbracoContext.ContentCache.GetById(
|
||||
RoutingContext.UmbracoContext.InPreviewMode,
|
||||
current.Id);
|
||||
|
||||
//re-assign
|
||||
PublishedContent = reGet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current variant info assigned to the request
|
||||
/// </summary>
|
||||
public VariantInfo VariantInfo { get; private set; }
|
||||
|
||||
#region Status
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,77 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ServiceModel;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides context for the routing of a request.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Provides context for the routing of a request.
|
||||
/// </summary>
|
||||
public class RoutingContext
|
||||
{
|
||||
private readonly Lazy<UrlProvider> _urlProvider;
|
||||
private readonly Lazy<IEnumerable<IContentFinder>> _publishedContentFinders;
|
||||
private readonly Lazy<IContentFinder> _publishedContentLastChanceFinder;
|
||||
|
||||
private readonly Lazy<UrlProvider> _urlProvider;
|
||||
private readonly Lazy<IEnumerable<IContentFinder>> _publishedContentFinders;
|
||||
private readonly Lazy<IContentFinder> _publishedContentLastChanceFinder;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutingContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"> </param>
|
||||
/// <param name="contentFinders">The document lookups resolver.</param>
|
||||
/// <param name="contentLastChanceFinder"> </param>
|
||||
/// <param name="urlProvider">The nice urls provider.</param>
|
||||
internal RoutingContext(
|
||||
UmbracoContext umbracoContext,
|
||||
IEnumerable<IContentFinder> contentFinders,
|
||||
IContentFinder contentLastChanceFinder,
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutingContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"> </param>
|
||||
/// <param name="contentFinders">The document lookups resolver.</param>
|
||||
/// <param name="contentLastChanceFinder"> </param>
|
||||
/// <param name="urlProvider">The nice urls provider.</param>
|
||||
internal RoutingContext(
|
||||
UmbracoContext umbracoContext,
|
||||
IEnumerable<IContentFinder> contentFinders,
|
||||
IContentFinder contentLastChanceFinder,
|
||||
UrlProvider urlProvider)
|
||||
: this(umbracoContext)
|
||||
{
|
||||
UmbracoContext = umbracoContext;
|
||||
_publishedContentFinders = new Lazy<IEnumerable<IContentFinder>>(() => contentFinders, false);
|
||||
_publishedContentLastChanceFinder = new Lazy<IContentFinder>(() => contentLastChanceFinder, false);
|
||||
_urlProvider = new Lazy<UrlProvider>(() => urlProvider, false);
|
||||
_urlProvider = new Lazy<UrlProvider>(() => urlProvider, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutingContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"></param>
|
||||
/// <param name="contentFinders"></param>
|
||||
/// <param name="contentLastChanceFinder"></param>
|
||||
/// <param name="urlProvider"></param>
|
||||
internal RoutingContext(
|
||||
UmbracoContext umbracoContext,
|
||||
Lazy<IEnumerable<IContentFinder>> contentFinders,
|
||||
Lazy<IContentFinder> contentLastChanceFinder,
|
||||
Lazy<UrlProvider> urlProvider)
|
||||
: this(umbracoContext)
|
||||
{
|
||||
UmbracoContext = umbracoContext;
|
||||
|
||||
_publishedContentFinders = contentFinders;
|
||||
_publishedContentLastChanceFinder = contentLastChanceFinder;
|
||||
_urlProvider = urlProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Umbraco context.
|
||||
/// </summary>
|
||||
public UmbracoContext UmbracoContext { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published content finders.
|
||||
/// </summary>
|
||||
internal IEnumerable<IContentFinder> PublishedContentFinders
|
||||
{
|
||||
get { return _publishedContentFinders.Value; }
|
||||
/// <summary>
|
||||
/// Creates the lazy assigned segments logic
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"></param>
|
||||
private RoutingContext(UmbracoContext umbracoContext)
|
||||
{
|
||||
UmbracoContext = umbracoContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published content last chance finder.
|
||||
/// </summary>
|
||||
internal IContentFinder PublishedContentLastChanceFinder
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Umbraco context.
|
||||
/// </summary>
|
||||
public UmbracoContext UmbracoContext { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published content finders.
|
||||
/// </summary>
|
||||
internal IEnumerable<IContentFinder> PublishedContentFinders
|
||||
{
|
||||
get { return _publishedContentFinders.Value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published content last chance finder.
|
||||
/// </summary>
|
||||
internal IContentFinder PublishedContentLastChanceFinder
|
||||
{
|
||||
get { return _publishedContentLastChanceFinder.Value; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the urls provider.
|
||||
/// </summary>
|
||||
public UrlProvider UrlProvider
|
||||
{
|
||||
get { return _urlProvider.Value; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the urls provider.
|
||||
/// </summary>
|
||||
public UrlProvider UrlProvider
|
||||
{
|
||||
get { return _urlProvider.Value; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Web;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
|
||||
namespace Umbraco.Web.Routing.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// Assigns segments based on MS's HttpBrowserCapabilities object
|
||||
/// </summary>
|
||||
[DisplayName("Browser capabilities provider")]
|
||||
[Description("Uses ASP.Net HttpBrowserCapabilities object to analyze the current request")]
|
||||
[ContentVariant("Mobile users", "IsMobileDevice")]
|
||||
[ContentVariant("Java applet people", "JavaApplets")]
|
||||
internal class BrowserCapabilitiesProvider : ContentSegmentProvider
|
||||
{
|
||||
public BrowserCapabilitiesProvider()
|
||||
{
|
||||
_browserCapabilityProps = typeof(HttpBrowserCapabilitiesBase).GetProperties()
|
||||
.Where(x => PropNames.Contains(x.Name))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private readonly PropertyInfo[] _browserCapabilityProps;
|
||||
|
||||
private static readonly string[] PropNames =
|
||||
{
|
||||
"Browser",
|
||||
"IsMobileDevice",
|
||||
"JavaApplets",
|
||||
"MajorVersion",
|
||||
"MinorVersion",
|
||||
"MobileDeviceModel",
|
||||
"MobileDeviceManufacturer",
|
||||
"Platform"
|
||||
};
|
||||
|
||||
public override SegmentCollection GetSegmentsForRequest(Uri originalRequestUrl, Uri cleanedRequestUrl, HttpRequestBase httpRequest)
|
||||
{
|
||||
return new SegmentCollection(
|
||||
_browserCapabilityProps
|
||||
.Select(x => new Segment(x.Name, x.GetValue(httpRequest.Browser, null))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
|
||||
namespace Umbraco.Web.Routing.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// Similar to a normal segment provider but this lets admins of Umbraco choose a custom key/value to store in the current request
|
||||
/// based on a value that the provider returns.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An example of such a provider would be a 'ReferalProvider' for which the provider itself will inspect the current request's referrer, the
|
||||
/// provider will return this value. If this provider is active, we will run the boolean logic configured for the provider
|
||||
/// which would normally be a regex statement, if it matches the returned value then we will apply the configured key/value as a segment
|
||||
/// in the request.
|
||||
///
|
||||
/// </remarks>
|
||||
public abstract class ConfigurableSegmentProvider : ContentSegmentProvider
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
|
||||
|
||||
/// <summary>
|
||||
/// Override to return the statically assigned variants for this provider as well as any segments
|
||||
/// that are configured to be variants.
|
||||
/// </summary>
|
||||
public override IEnumerable<ContentVariantAttribute> AssignableContentVariants
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.AssignableContentVariants.Union(
|
||||
ReadSegmentConfiguration()
|
||||
.Where(x => x.AllowedAsVariant)
|
||||
.Select(x => new ContentVariantAttribute(x.Key, x.Key, x.Value)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current provider's value (i.e. if the provider was a referal provider, this would return the current referrer)
|
||||
/// </summary>
|
||||
public abstract object GetCurrentValue(Uri cleanedRequestUrl, HttpRequestBase httpRequest);
|
||||
|
||||
/// <summary>
|
||||
/// By default this uses a regex statement to match but inheritors could do anything they want (i.e. dynamic compilation)
|
||||
/// </summary>
|
||||
/// <param name="matchStatement"></param>
|
||||
/// <param name="cleanedRequestUrl"></param>
|
||||
/// <param name="httpRequest"></param>
|
||||
/// <returns></returns>
|
||||
public virtual bool IsMatch(string matchStatement, Uri cleanedRequestUrl,
|
||||
HttpRequestBase httpRequest)
|
||||
{
|
||||
if (matchStatement == null) throw new ArgumentNullException("matchStatement");
|
||||
if (cleanedRequestUrl == null) throw new ArgumentNullException("cleanedRequestUrl");
|
||||
if (httpRequest == null) throw new ArgumentNullException("httpRequest");
|
||||
|
||||
var val = GetCurrentValue(cleanedRequestUrl, httpRequest);
|
||||
if (val == null) return false;
|
||||
|
||||
return Regex.IsMatch(val.ToString(), matchStatement);
|
||||
}
|
||||
|
||||
public override SegmentCollection GetSegmentsForRequest(Uri originalRequestUrl,
|
||||
Uri cleanedRequestUrl,
|
||||
HttpRequestBase httpRequest)
|
||||
{
|
||||
if (originalRequestUrl == null) throw new ArgumentNullException("originalRequestUrl");
|
||||
if (cleanedRequestUrl == null) throw new ArgumentNullException("cleanedRequestUrl");
|
||||
if (httpRequest == null) throw new ArgumentNullException("httpRequest");
|
||||
|
||||
var config = ReadSegmentConfiguration();
|
||||
var type = this.GetType();
|
||||
var result = config
|
||||
.Where(match => IsMatch(match.MatchExpression, cleanedRequestUrl, httpRequest))
|
||||
.Select(match => new Segment(match.Key, match.Value, match.Persist));
|
||||
|
||||
return new SegmentCollection(result);
|
||||
}
|
||||
|
||||
public IEnumerable<SegmentProviderMatch> ReadSegmentConfiguration()
|
||||
{
|
||||
using (new ReadLock(_lock))
|
||||
{
|
||||
var fileName = IOHelper.MapPath("~/App_Data/Segments/" + GetType().Namespace.EnsureEndsWith('.') + GetType().Name + ".segments.json");
|
||||
if (File.Exists(fileName) == false) return Enumerable.Empty<SegmentProviderMatch>();
|
||||
var content = File.ReadAllText(fileName);
|
||||
var result = JsonConvert.DeserializeObject<IEnumerable<SegmentProviderMatch>>(content);
|
||||
//remove any entries without keys - safety check
|
||||
return result.Where(x => x.Key.IsNullOrWhiteSpace() == false);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteSegmentConfiguration(IEnumerable<SegmentProviderMatch> config)
|
||||
{
|
||||
using (new WriteLock(_lock))
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(config);
|
||||
var fileName = GetType().Namespace.EnsureEndsWith('.') + GetType().Name + ".segments.json";
|
||||
Directory.CreateDirectory(IOHelper.MapPath("~/App_Data/Segments"));
|
||||
File.WriteAllText(IOHelper.MapPath("~/App_Data/Segments/" + fileName), json);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
|
||||
namespace Umbraco.Web.Routing.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the segment names to assign to the current request
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The provider also exposes via attributes which static segments can be applied to content variations
|
||||
/// </remarks>
|
||||
public abstract class ContentSegmentProvider
|
||||
{
|
||||
protected ContentSegmentProvider()
|
||||
{
|
||||
//ensure attributes exist
|
||||
var type = GetType();
|
||||
var nameAtt = type.GetCustomAttribute<DisplayNameAttribute>(false);
|
||||
var descAtt = type.GetCustomAttribute<DescriptionAttribute>(false);
|
||||
if (nameAtt == null || descAtt == null)
|
||||
{
|
||||
throw new ApplicationException(
|
||||
"The segment provider " + type + " must be attributed with two attributes: " + typeof(DisplayNameAttribute) + " and " + typeof(DescriptionAttribute));
|
||||
}
|
||||
Name = nameAtt.DisplayName;
|
||||
Description = descAtt.Description;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Content Variants that this provider supports
|
||||
/// </summary>
|
||||
public virtual IEnumerable<ContentVariantAttribute> AssignableContentVariants
|
||||
{
|
||||
get { return GetType().GetCustomAttributes<ContentVariantAttribute>(false).ToArray(); }
|
||||
}
|
||||
|
||||
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the status of enabled/disabled variants for a given provider
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IDictionary<string, bool> ReadVariantConfiguration()
|
||||
{
|
||||
using (new ReadLock(_lock))
|
||||
{
|
||||
var fileName = IOHelper.MapPath("~/App_Data/Segments/" + GetType().Namespace.EnsureEndsWith('.') + GetType().Name + ".variants.json");
|
||||
if (File.Exists(fileName) == false) return new Dictionary<string, bool>();
|
||||
var contents = File.ReadAllText(fileName);
|
||||
var result = JsonConvert.DeserializeObject<IDictionary<string, bool>>(contents);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the status of enabled/disabled variants for a given provider
|
||||
/// </summary>
|
||||
/// <param name="variantConfig"></param>
|
||||
public void WriteVariantConfiguration(IDictionary<string, bool> variantConfig)
|
||||
{
|
||||
using (new WriteLock(_lock))
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(variantConfig);
|
||||
var fileName = GetType().Namespace.EnsureEndsWith('.') + GetType().Name + ".variants.json";
|
||||
Directory.CreateDirectory(IOHelper.MapPath("~/App_Data/Segments"));
|
||||
File.WriteAllText(IOHelper.MapPath("~/App_Data/Segments/" + fileName), json);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the segment names and values to assign to the current request
|
||||
/// </summary>
|
||||
/// <param name="originalRequestUrl"></param>
|
||||
/// <param name="cleanedRequestUrl"></param>
|
||||
/// <param name="httpRequest"></param>
|
||||
/// <returns></returns>
|
||||
public abstract SegmentCollection GetSegmentsForRequest(
|
||||
Uri originalRequestUrl,
|
||||
Uri cleanedRequestUrl,
|
||||
HttpRequestBase httpRequest);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Web.Routing.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolver for content variation providers
|
||||
/// </summary>
|
||||
public sealed class ContentSegmentProviderResolver : ManyObjectsResolverBase<ContentSegmentProviderResolver, ContentSegmentProvider>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentSegmentProviderResolver"/> class with
|
||||
/// an initial list of provider types.
|
||||
/// </summary>
|
||||
/// <param name="converters">The list of provider types</param>
|
||||
/// <remarks>The resolver is created by the <c>WebBootManager</c> and thus the constructor remains internal.</remarks>
|
||||
internal ContentSegmentProviderResolver(IEnumerable<Type> converters)
|
||||
: base(converters)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentSegmentProviderResolver"/> class with
|
||||
/// an initial list of provider types.
|
||||
/// </summary>
|
||||
/// <param name="converters">The list of provider types</param>
|
||||
/// <remarks>The resolver is created by the <c>WebBootManager</c> and thus the constructor remains internal.</remarks>
|
||||
internal ContentSegmentProviderResolver(params Type[] converters)
|
||||
: base(converters)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the providers.
|
||||
/// </summary>
|
||||
public IEnumerable<ContentSegmentProvider> Providers
|
||||
{
|
||||
get { return Values; }
|
||||
}
|
||||
|
||||
public IEnumerable<ContentVariantAttribute> GetAssignableVariants(IDictionary<string, bool> segmentProviderStatus)
|
||||
{
|
||||
//These are the assignable variants based on the installed providers (statically advertised variants) or
|
||||
// configured segments that have been flagged as variants
|
||||
// that are enabled via the back office. If they are not enabled, they will not show up.
|
||||
|
||||
var assignableSegments = this.Providers
|
||||
//don't lookup anything in any providers that are not enabled
|
||||
.Where(provider => segmentProviderStatus[provider.GetType().FullName] == true)
|
||||
.Select(provider => new
|
||||
{
|
||||
instance = provider,
|
||||
//get the keys that have been allowed
|
||||
enabledVariants = provider.ReadVariantConfiguration()
|
||||
.Where(vari => vari.Value) // the value == true
|
||||
.Select(vari => vari.Key).ToArray() // get the key
|
||||
})
|
||||
//only allow the onces that are enabled
|
||||
.SelectMany(x =>
|
||||
x.instance.AssignableContentVariants.Where(vari => x.enabledVariants.Contains(vari.SegmentMatchKey)));
|
||||
|
||||
return assignableSegments;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Web.Routing.Segments
|
||||
{
|
||||
|
||||
public class ContentSegmentProvidersStatus
|
||||
{
|
||||
private readonly static ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary containing the provider type name and whether it is enabled or not
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IDictionary<string, bool> GetProviderStatus()
|
||||
{
|
||||
using (new ReadLock(Lock))
|
||||
{
|
||||
var file = IOHelper.MapPath("~/App_Data/Segments/providers.config.json");
|
||||
if (File.Exists(file) == false)
|
||||
{
|
||||
//empty
|
||||
return new Dictionary<string, bool>();
|
||||
}
|
||||
var contents = File.ReadAllText(file);
|
||||
var result = JsonConvert.DeserializeObject<IDictionary<string, bool>>(contents);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveProvidersStatus(IDictionary<string, bool> providersStatus)
|
||||
{
|
||||
using (new WriteLock(Lock))
|
||||
{
|
||||
var file = IOHelper.MapPath("~/App_Data/Segments/providers.config.json");
|
||||
var result = JsonConvert.SerializeObject(providersStatus);
|
||||
Directory.CreateDirectory(IOHelper.MapPath("~/App_Data/Segments"));
|
||||
File.WriteAllText(file, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Web.Routing.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a custom Content Variant exposed by a segment provider
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public sealed class ContentVariantAttribute : Attribute
|
||||
{
|
||||
public string VariantName { get; set; }
|
||||
public string SegmentMatchKey { get; set; }
|
||||
public object SegmentMatchValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor used to specify the content variant name and the segment key to match, in
|
||||
/// order for the request to match on this variant the value stored against the segmentMatchKey must
|
||||
/// be a boolean and must be true.
|
||||
/// </summary>
|
||||
/// <param name="variantName"></param>
|
||||
/// <param name="segmentMatchKey"></param>
|
||||
public ContentVariantAttribute(string variantName, string segmentMatchKey)
|
||||
{
|
||||
VariantName = variantName;
|
||||
SegmentMatchKey = segmentMatchKey;
|
||||
|
||||
//must equal true for this overload
|
||||
SegmentMatchValue = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor used to specify the content variant name and the segment key to match, in
|
||||
/// order for the request to match on this variant the value stored against the segmentMatchKey must
|
||||
/// be equal to the segmentMatchValue specified
|
||||
/// </summary>
|
||||
/// <param name="variantName"></param>
|
||||
/// <param name="segmentMatchKey"></param>
|
||||
/// <param name="segmentMatchValue"></param>
|
||||
public ContentVariantAttribute(string variantName, string segmentMatchKey, object segmentMatchValue)
|
||||
{
|
||||
VariantName = variantName;
|
||||
SegmentMatchKey = segmentMatchKey;
|
||||
SegmentMatchValue = segmentMatchValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Web;
|
||||
|
||||
namespace Umbraco.Web.Routing.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// A configurable provider to match against the referrer
|
||||
/// </summary>
|
||||
[DisplayName("Referrer provider")]
|
||||
[Description("A configurable provider that analyzes the current request's referrer")]
|
||||
public class ReferrerSegmentProvider : ConfigurableSegmentProvider
|
||||
{
|
||||
public override object GetCurrentValue(Uri cleanedRequestUrl, HttpRequestBase httpRequest)
|
||||
{
|
||||
return httpRequest.UrlReferrer == null ? "" : httpRequest.UrlReferrer.OriginalString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
|
||||
namespace Umbraco.Web.Routing.Segments
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to retreive (and persist) the segments that have been found based on the current request information
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When retreiving the segments, this is done lazily and during that execution it will also persist the
|
||||
/// matched advertised values to a cookie.
|
||||
///
|
||||
/// This class is NOT thread safe
|
||||
///
|
||||
/// </remarks>
|
||||
public class RequestSegments
|
||||
{
|
||||
private readonly IEnumerable<ContentSegmentProvider> _segmentProviders;
|
||||
|
||||
private readonly Lazy<SegmentCollection> _assignedSegments;
|
||||
|
||||
public RequestSegments(IEnumerable<ContentSegmentProvider> segmentProviders,
|
||||
Uri originalRequestUrl,
|
||||
Uri cleanedRequestUrl,
|
||||
HttpRequestBase httpRequest)
|
||||
{
|
||||
_segmentProviders = segmentProviders;
|
||||
|
||||
_assignedSegments = new Lazy<SegmentCollection>(() =>
|
||||
GetAllSegmentsForRequest(
|
||||
_segmentProviders,
|
||||
originalRequestUrl,
|
||||
cleanedRequestUrl,
|
||||
httpRequest,
|
||||
ContentSegmentProvidersStatus.GetProviderStatus()),
|
||||
//This class should ONLY be used by one thread at a time (i.e. current request)
|
||||
LazyThreadSafetyMode.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will add a segment to the current instance - this is only used to manually add a segment during a request when
|
||||
/// using the MemberSegment and is for performance improvements so we don't have to go to the db on each usage of MemberSegment.
|
||||
/// </summary>
|
||||
/// <param name="segment"></param>
|
||||
internal void Add(Segment segment)
|
||||
{
|
||||
_assignedSegments.Value.AddNew(segment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method must be called in order to persist the segments to cookie
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will not overwrite cookie data that isn't contained in the current request segments
|
||||
/// </remarks>
|
||||
internal void EnsurePersisted(HttpResponseBase response, HttpRequestBase request)
|
||||
{
|
||||
//NOTE: The cookie data will alraedy be part of the PersistedSegments (see GetAllSegmentsForRequest)
|
||||
|
||||
var toPersist = PersistedSegments.ToList();
|
||||
|
||||
if (toPersist.Any())
|
||||
{
|
||||
|
||||
var json = JsonConvert.SerializeObject(toPersist);
|
||||
|
||||
//TODO: Implement the expiry dates in each individual segment - otherwise
|
||||
// what is going to happen is that any persisted segment will have a sliding expiration
|
||||
// of 30 days!
|
||||
|
||||
var cookie = new HttpCookie(Constants.Web.SegmentCookieName, json)
|
||||
{
|
||||
//sliding 30 day expiry?
|
||||
Expires = DateTime.Now.AddDays(30),
|
||||
HttpOnly = true
|
||||
};
|
||||
response.SetCookie(cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.SetCookie(new HttpCookie(Constants.Web.SegmentCookieName)
|
||||
{
|
||||
//remove it!
|
||||
Expires = DateTime.Now.AddDays(-30)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the assigned segments for the current request
|
||||
/// </summary>
|
||||
public IEnumerable<Segment> AssignedSegments
|
||||
{
|
||||
get { return _assignedSegments.Value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the segments that are persisted (cookie and should be part of the member profile)
|
||||
/// </summary>
|
||||
internal IEnumerable<Segment> PersistedSegments
|
||||
{
|
||||
get { return _assignedSegments.Value.Where(x => x.Persist); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the segment key exists in the request
|
||||
/// </summary>
|
||||
/// <param name="segmentKey"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Example: RequestIs("Mobile") if a segment key is "Mobile" and it's value is a boolean true.
|
||||
/// </remarks>
|
||||
public bool RequestIs(string segmentKey)
|
||||
{
|
||||
return AssignedSegments.Any(x => x.Key == segmentKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if any assigned segment key + value matches the specified parameters
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="val"></param>
|
||||
/// <returns></returns>
|
||||
public bool RequestEquals(string key, object val)
|
||||
{
|
||||
return AssignedSegments.Any(x => x.Key == key && x.Value.Equals(val));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal so it can be tested - collects all segments from providers and the ones in the cookie
|
||||
/// </summary>
|
||||
/// <param name="segmentProviders"></param>
|
||||
/// <param name="originalRequestUrl"></param>
|
||||
/// <param name="cleanedRequestUrl"></param>
|
||||
/// <param name="httpRequest"></param>
|
||||
/// <param name="providersStatus"></param>
|
||||
/// <returns>
|
||||
/// </returns>
|
||||
internal static SegmentCollection GetAllSegmentsForRequest(
|
||||
IEnumerable<ContentSegmentProvider> segmentProviders,
|
||||
Uri originalRequestUrl,
|
||||
Uri cleanedRequestUrl,
|
||||
HttpRequestBase httpRequest,
|
||||
IDictionary<string, bool> providersStatus)
|
||||
{
|
||||
//get all key/vals, there might be duplicates so we will simply take the last one in
|
||||
|
||||
var d = new List<Segment>();
|
||||
|
||||
foreach (var provider in segmentProviders
|
||||
.Select(x => new
|
||||
{
|
||||
instance = x,
|
||||
typeName = x.GetType().FullName
|
||||
})
|
||||
//ensure it is enabled
|
||||
.Where(x => providersStatus.ContainsKey(x.typeName) && providersStatus[x.typeName])
|
||||
.Select(x => x.instance))
|
||||
{
|
||||
var segments = provider.GetSegmentsForRequest(originalRequestUrl, cleanedRequestUrl, httpRequest).ToArray();
|
||||
|
||||
d.AddRange(segments);
|
||||
}
|
||||
|
||||
var cookieData = httpRequest.Cookies[Constants.Web.SegmentCookieName] == null
|
||||
? new List<Segment>()
|
||||
//TODO: try/catch
|
||||
: JsonConvert.DeserializeObject<IEnumerable<Segment>>(httpRequest.Cookies[Constants.Web.SegmentCookieName].Value);
|
||||
|
||||
//Add anything in the cookie that is not already in the list
|
||||
d.AddRange(cookieData.Where(x => d.Contains(x) == false));
|
||||
|
||||
return new SegmentCollection(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using umbraco;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
internal static class UrlProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the URLs for the content item
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Use this when displaying URLs, if there are errors genertaing the urls the urls themselves will
|
||||
/// contain the errors.
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> GetContentUrls(this IContent content)
|
||||
{
|
||||
public static IEnumerable<string> GetContentUrls(this IContent content, IUser user, UrlProvider urlProvider)
|
||||
{
|
||||
var urls = new List<string>();
|
||||
|
||||
if (content.HasPublishedVersion() == false)
|
||||
{
|
||||
urls.Add(ui.Text("content", "itemNotPublished", UmbracoContext.Current.Security.CurrentUser));
|
||||
urls.Add(ui.Text("content", "itemNotPublished", user));
|
||||
return urls;
|
||||
}
|
||||
|
||||
var urlProvider = UmbracoContext.Current.RoutingContext.UrlProvider;
|
||||
var url = urlProvider.GetUrl(content.Id);
|
||||
var url = urlProvider.GetUrl(content.Id);
|
||||
if (url == "#")
|
||||
{
|
||||
// document as a published version yet it's url is "#" => a parent must be
|
||||
@@ -39,9 +31,9 @@ namespace Umbraco.Web.Routing
|
||||
while (parent != null && parent.Published);
|
||||
|
||||
if (parent == null) // oops - internal error
|
||||
urls.Add(ui.Text("content", "parentNotPublishedAnomaly", UmbracoContext.Current.Security.CurrentUser));
|
||||
urls.Add(ui.Text("content", "parentNotPublishedAnomaly", user));
|
||||
else
|
||||
urls.Add(ui.Text("content", "parentNotPublished", parent.Name, UmbracoContext.Current.Security.CurrentUser));
|
||||
urls.Add(ui.Text("content", "parentNotPublished", parent.Name, user));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -50,5 +42,21 @@ namespace Umbraco.Web.Routing
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URLs for the content item
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Use this when displaying URLs, if there are errors genertaing the urls the urls themselves will
|
||||
/// contain the errors.
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> GetContentUrls(this IContent content)
|
||||
{
|
||||
return content.GetContentUrls(
|
||||
UmbracoContext.Current.Security.CurrentUser,
|
||||
UmbracoContext.Current.RoutingContext.UrlProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,10 +342,18 @@ namespace Umbraco.Web.Search
|
||||
DeleteIndexForEntity(payload.Id, false);
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
case UnpublishedPageCacheRefresher.OperationType.Saved:
|
||||
|
||||
if (payload.Id > 0)
|
||||
{
|
||||
var c5 = ApplicationContext.Current.Services.ContentService.GetById(payload.Id);
|
||||
ReIndexForContent(c5, false);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// This is similar tothe RequestSegments class, however the member segments is not just based on current request data, this will check for the segments
|
||||
/// based on the current request, the segments cookie and finally the member instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Ensure this class is only created in a Request scope!
|
||||
/// </remarks>
|
||||
public class MemberSegments
|
||||
{
|
||||
private readonly MembershipHelper _membershipHelper;
|
||||
private readonly RequestSegments _reguestSegments;
|
||||
private readonly ServiceContext _services;
|
||||
|
||||
public MemberSegments(MembershipHelper membershipHelper, RequestSegments reguestSegments, ServiceContext services)
|
||||
{
|
||||
if (membershipHelper == null) throw new ArgumentNullException("membershipHelper");
|
||||
if (reguestSegments == null) throw new ArgumentNullException("reguestSegments");
|
||||
if (services == null) throw new ArgumentNullException("services");
|
||||
_membershipHelper = membershipHelper;
|
||||
_reguestSegments = reguestSegments;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public IEnumerable<Segment> AssignedSegments
|
||||
{
|
||||
get
|
||||
{
|
||||
var requestAssigned = _reguestSegments.AssignedSegments.ToArray();
|
||||
var memberPersisted = PersistedSegments;
|
||||
|
||||
//combine and return, the request supercedes the persisted
|
||||
return requestAssigned.Union(memberPersisted.Except(requestAssigned));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Is(string segmentKey)
|
||||
{
|
||||
//this will check the request (+ cookie)
|
||||
var contains = _reguestSegments.RequestIs(segmentKey);
|
||||
//if the request has the key, then return from the request
|
||||
if (contains) return _reguestSegments.RequestIs(segmentKey);
|
||||
|
||||
//lookup from member
|
||||
var fromMember = GetSegmentByKeyFromMember(segmentKey);
|
||||
return fromMember != null;
|
||||
}
|
||||
|
||||
public bool Equals(string segmentKey, object val)
|
||||
{
|
||||
//this will check the request (+ cookie)
|
||||
var contains = _reguestSegments.RequestIs(segmentKey);
|
||||
//if the request has the key, then return from the request
|
||||
if (contains) return _reguestSegments.RequestEquals(segmentKey, val);
|
||||
|
||||
//lookup from member
|
||||
var fromMember = GetSegmentByKeyFromMember(segmentKey);
|
||||
return fromMember != null && fromMember.Value == val;
|
||||
}
|
||||
|
||||
private IMember _member;
|
||||
private bool _hasLoaded = false;
|
||||
/// <summary>
|
||||
/// Lazy loads and only loads once per request
|
||||
/// </summary>
|
||||
internal IMember CurrentMember
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_member == null && _hasLoaded == false)
|
||||
{
|
||||
var memberId = _membershipHelper.GetCurrentMemberId();
|
||||
if (memberId > 0)
|
||||
{
|
||||
_member = _services.MemberService.GetById(memberId);
|
||||
}
|
||||
_hasLoaded = true;
|
||||
}
|
||||
return _member;
|
||||
}
|
||||
}
|
||||
|
||||
private Segment[] _persistedSegments;
|
||||
/// <summary>
|
||||
/// Lazy loads the persisted once per request
|
||||
/// </summary>
|
||||
internal IEnumerable<Segment> PersistedSegments
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_persistedSegments == null && CurrentMember != null && CurrentMember.HasProperty(Constants.Conventions.Member.Segments))
|
||||
{
|
||||
var val = CurrentMember.Properties[Constants.Conventions.Member.Segments].Value.ToString();
|
||||
_persistedSegments = JsonConvert.DeserializeObject<Segment[]>(val);
|
||||
}
|
||||
return (_persistedSegments == null || _persistedSegments.Length == 0)
|
||||
? Enumerable.Empty<Segment>()
|
||||
: _persistedSegments;
|
||||
}
|
||||
}
|
||||
|
||||
private Segment GetSegmentByKeyFromMember(string segmentKey)
|
||||
{
|
||||
var segment = PersistedSegments.FirstOrDefault(x => x.Key == segmentKey);
|
||||
if (segment == null) return null;
|
||||
|
||||
//add it to the request so we don't have to lookup in db again
|
||||
_reguestSegments.Add(segment);
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -9,8 +10,11 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
using MPE = global::Umbraco.Core.Security.MembershipProviderExtensions;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
@@ -21,24 +25,46 @@ namespace Umbraco.Web.Security
|
||||
/// </summary>
|
||||
public class MembershipHelper
|
||||
{
|
||||
private readonly ApplicationContext _applicationContext;
|
||||
private readonly ServiceContext _services;
|
||||
private readonly HttpContextBase _httpContext;
|
||||
private readonly RequestSegments _requestSegments;
|
||||
|
||||
#region Constructors
|
||||
|
||||
[Obsolete("Use the other constructor accepting a RequestSegments instance")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public MembershipHelper(ApplicationContext applicationContext, HttpContextBase httpContext)
|
||||
: this(applicationContext.Services, httpContext, UmbracoContext.Current.RequestSegments)
|
||||
{
|
||||
if (applicationContext == null) throw new ArgumentNullException("applicationContext");
|
||||
if (httpContext == null) throw new ArgumentNullException("httpContext");
|
||||
_applicationContext = applicationContext;
|
||||
_httpContext = httpContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="requestSegments"></param>
|
||||
public MembershipHelper(ServiceContext services, HttpContextBase httpContext, RequestSegments requestSegments)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException("services");
|
||||
if (httpContext == null) throw new ArgumentNullException("httpContext");
|
||||
_services = services;
|
||||
_httpContext = httpContext;
|
||||
_requestSegments = requestSegments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"></param>
|
||||
public MembershipHelper(UmbracoContext umbracoContext)
|
||||
{
|
||||
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
||||
_httpContext = umbracoContext.HttpContext;
|
||||
_applicationContext = umbracoContext.Application;
|
||||
_services = umbracoContext.Application.Services;
|
||||
_requestSegments = umbracoContext.RequestSegments;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@@ -111,7 +137,7 @@ namespace Umbraco.Web.Security
|
||||
}
|
||||
}
|
||||
|
||||
_applicationContext.Services.MemberService.Save(member);
|
||||
_services.MemberService.Save(member);
|
||||
|
||||
//reset the FormsAuth cookie since the username might have changed
|
||||
FormsAuthentication.SetAuthCookie(member.Username, true);
|
||||
@@ -146,7 +172,7 @@ namespace Umbraco.Web.Security
|
||||
|
||||
if (status != MembershipCreateStatus.Success) return null;
|
||||
|
||||
var member = _applicationContext.Services.MemberService.GetByUsername(membershipUser.UserName);
|
||||
var member = _services.MemberService.GetByUsername(membershipUser.UserName);
|
||||
member.Name = model.Name;
|
||||
|
||||
if (model.MemberProperties != null)
|
||||
@@ -158,7 +184,7 @@ namespace Umbraco.Web.Security
|
||||
}
|
||||
}
|
||||
|
||||
_applicationContext.Services.MemberService.Save(member);
|
||||
_services.MemberService.Save(member);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -224,7 +250,7 @@ namespace Umbraco.Web.Security
|
||||
throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active");
|
||||
}
|
||||
|
||||
var result = _applicationContext.Services.MemberService.GetByProviderKey(key);
|
||||
var result = _services.MemberService.GetByProviderKey(key);
|
||||
return result == null ? null : new MemberPublishedContent(result, provider.GetUser(result.Username, false));
|
||||
}
|
||||
|
||||
@@ -236,7 +262,7 @@ namespace Umbraco.Web.Security
|
||||
throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active");
|
||||
}
|
||||
|
||||
var result = _applicationContext.Services.MemberService.GetById(memberId);
|
||||
var result = _services.MemberService.GetById(memberId);
|
||||
return result == null ? null : new MemberPublishedContent(result, provider.GetUser(result.Username, false));
|
||||
}
|
||||
|
||||
@@ -248,7 +274,7 @@ namespace Umbraco.Web.Security
|
||||
throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active");
|
||||
}
|
||||
|
||||
var result = _applicationContext.Services.MemberService.GetByUsername(username);
|
||||
var result = _services.MemberService.GetByUsername(username);
|
||||
return result == null ? null : new MemberPublishedContent(result, provider.GetUser(result.Username, false));
|
||||
}
|
||||
|
||||
@@ -260,7 +286,7 @@ namespace Umbraco.Web.Security
|
||||
throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active");
|
||||
}
|
||||
|
||||
var result = _applicationContext.Services.MemberService.GetByEmail(email);
|
||||
var result = _services.MemberService.GetByEmail(email);
|
||||
return result == null ? null : new MemberPublishedContent(result, provider.GetUser(result.Username, false));
|
||||
}
|
||||
|
||||
@@ -366,7 +392,7 @@ namespace Umbraco.Web.Security
|
||||
if (provider.IsUmbracoMembershipProvider())
|
||||
{
|
||||
memberTypeAlias = memberTypeAlias ?? Constants.Conventions.MemberTypes.DefaultAlias;
|
||||
var memberType = _applicationContext.Services.MemberTypeService.Get(memberTypeAlias);
|
||||
var memberType = _services.MemberTypeService.Get(memberTypeAlias);
|
||||
if (memberType == null)
|
||||
throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias);
|
||||
|
||||
@@ -789,6 +815,17 @@ namespace Umbraco.Web.Security
|
||||
return Attempt<MembershipUser>.Fail(member);
|
||||
}
|
||||
|
||||
#region Segments
|
||||
|
||||
private MemberSegments _segments;
|
||||
public MemberSegments Segments
|
||||
{
|
||||
get { return _segments ?? (_segments = new MemberSegments(this, _requestSegments, _services)); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently logged in IMember object - this should never be exposed to the front-end since it's returning a business logic entity!
|
||||
/// </summary>
|
||||
@@ -802,9 +839,10 @@ namespace Umbraco.Web.Security
|
||||
throw new NotSupportedException("An IMember model can only be retreived when using the built-in Umbraco membership providers");
|
||||
}
|
||||
var username = provider.GetCurrentUserName();
|
||||
var member = _applicationContext.Services.MemberService.GetByUsername(username);
|
||||
var member = _services.MemberService.GetByUsername(username);
|
||||
return member;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,13 @@ namespace Umbraco.Web.Strategies.Publishing
|
||||
/// </summary>
|
||||
private void UpdateMultipleContentCache(IEnumerable<IContent> content)
|
||||
{
|
||||
DistributedCache.Instance.RefreshPageCache(content.ToArray());
|
||||
var asArray = content.ToArray();
|
||||
var ids = asArray.Select(x => x.Id).ToList();
|
||||
//ensure the variants are refreshed too
|
||||
ids.AddRange(asArray.SelectMany(x => x.VariantInfo.VariantIds));
|
||||
ids.AddRange(asArray.Select(x => x.VariantInfo.MasterDocId));
|
||||
|
||||
DistributedCache.Instance.RefreshPageCache(ids.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,7 +76,11 @@ namespace Umbraco.Web.Strategies.Publishing
|
||||
/// </summary>
|
||||
private void UpdateSingleContentCache(IContent content)
|
||||
{
|
||||
DistributedCache.Instance.RefreshPageCache(content);
|
||||
var ids = new List<int> {content.Id};
|
||||
//ensure the variants are refreshed too
|
||||
ids.AddRange(content.VariantInfo.VariantIds);
|
||||
ids.Add(content.VariantInfo.MasterDocId);
|
||||
DistributedCache.Instance.RefreshPageCache(ids.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -53,7 +54,11 @@ namespace Umbraco.Web.Strategies.Publishing
|
||||
/// </summary>
|
||||
private void UnPublishSingle(IContent content)
|
||||
{
|
||||
DistributedCache.Instance.RemovePageCache(content);
|
||||
var ids = new List<int> { content.Id };
|
||||
//ensure the variants unpublished too if it's a master
|
||||
ids.AddRange(content.VariantInfo.VariantIds);
|
||||
|
||||
DistributedCache.Instance.RemovePageCache(ids.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,8 +77,26 @@ namespace Umbraco.Web.Trees
|
||||
protected override TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
var nodes = new TreeNodeCollection();
|
||||
var entities = GetChildEntities(id);
|
||||
nodes.AddRange(entities.Select(entity => GetSingleTreeNode(entity, id, queryStrings)).Where(node => node != null));
|
||||
var entities = GetChildEntities(id).ToArray();
|
||||
|
||||
//we don't want to show children that are variants
|
||||
int iid;
|
||||
if (int.TryParse(id, out iid) == false)
|
||||
{
|
||||
throw new InvalidCastException("The id for the media tree must be an integer");
|
||||
}
|
||||
|
||||
//all ids of the entities to be rendered
|
||||
var entityIds = entities.Select(x => x.Id).ToArray();
|
||||
//now go lookup all relations of type umbContentVariants by child id, anything returned is
|
||||
// a variant (not a master doc) so we need to exclude them.
|
||||
var variantIds = Services.RelationService.GetByChildIds(entityIds, "umbContentVariants")
|
||||
.Select(x => x.ChildId)
|
||||
.ToArray();
|
||||
//these are the master docs
|
||||
var nonVariants = entities.Where(x => variantIds.Contains(x.Id) == false);
|
||||
|
||||
nodes.AddRange(nonVariants.Select(entity => GetSingleTreeNode(entity, id, queryStrings)).Where(node => node != null));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@@ -122,6 +140,13 @@ namespace Umbraco.Web.Trees
|
||||
if (Access.IsProtected(e.Id, e.Path))
|
||||
node.SetProtectedStyle();
|
||||
|
||||
//add some data describing that this node is a variant
|
||||
var variantRelation = Services.RelationService.GetByChild(entity, "umbContentVariants").FirstOrDefault();
|
||||
if (variantRelation != null)
|
||||
{
|
||||
node.AdditionalData["masterDocId"] = variantRelation.ParentId;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Umbraco.Web.Trees
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <returns></returns>
|
||||
[HttpQueryStringFilter("queryStrings")]
|
||||
public TreeNode GetTreeNode(string id, FormDataCollection queryStrings)
|
||||
public virtual TreeNode GetTreeNode(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
int asInt;
|
||||
if (int.TryParse(id, out asInt) == false)
|
||||
|
||||
@@ -307,6 +307,7 @@
|
||||
<Compile Include="Editors\EntityControllerActionSelector.cs" />
|
||||
<Compile Include="Editors\EntityControllerConfigurationAttribute.cs" />
|
||||
<Compile Include="Editors\ImagesController.cs" />
|
||||
<Compile Include="Editors\SegmentDashboardController.cs" />
|
||||
<Compile Include="Models\ContentEditing\ContentBaseItemSave.cs" />
|
||||
<Compile Include="Models\ContentEditing\MediaItemSave.cs" />
|
||||
<Compile Include="Models\DetachedContent.cs" />
|
||||
@@ -357,6 +358,7 @@
|
||||
<Compile Include="Models\PartialViewMacroModelExtensions.cs" />
|
||||
<Compile Include="Models\PostRedirectModel.cs" />
|
||||
<Compile Include="Models\PublishedProperty.cs" />
|
||||
<Compile Include="Models\Segments\ContentVariableSegment.cs" />
|
||||
<Compile Include="Mvc\ActionExecutedEventArgs.cs" />
|
||||
<Compile Include="Mvc\NotFoundHandler.cs" />
|
||||
<Compile Include="Mvc\PreRenderViewActionFilterAttribute.cs" />
|
||||
@@ -470,7 +472,22 @@
|
||||
<Compile Include="PublishedContentQuery.cs" />
|
||||
<Compile Include="ImageCropperTemplateExtensions.cs" />
|
||||
<Compile Include="Mvc\UmbracoVirtualNodeRouteHandler.cs" />
|
||||
<Compile Include="Routing\Segments\ConfigurableSegmentProvider.cs" />
|
||||
<Compile Include="Routing\Segments\ContentSegmentProvidersStatus.cs" />
|
||||
<Compile Include="Routing\Segments\ReferrerSegmentProvider.cs" />
|
||||
<Compile Include="Routing\Segments\BrowserCapabilitiesProvider.cs" />
|
||||
|
||||
<Compile Include="Routing\Segments\RequestSegments.cs" />
|
||||
<Compile Include="Routing\Segments\ContentVariantAttribute.cs" />
|
||||
<Compile Include="Routing\Segments\ContentSegmentProvider.cs" />
|
||||
<Compile Include="Routing\Segments\ContentSegmentProviderResolver.cs" />
|
||||
<Compile Include="Models\Segments\Segment.cs" />
|
||||
<Compile Include="Models\Segments\SegmentCollection.cs" />
|
||||
<Compile Include="Models\Segments\SegmentProviderMatch.cs" />
|
||||
<Compile Include="Models\Segments\SegmentCollection.cs" />
|
||||
<Compile Include="Models\Segments\SegmentProviderMatch.cs" />
|
||||
<Compile Include="Routing\UrlProviderExtensions.cs" />
|
||||
<Compile Include="Security\MemberSegments.cs" />
|
||||
<Compile Include="Strategies\Migrations\ClearCsrfCookiesAfterUpgrade.cs" />
|
||||
<Compile Include="Strategies\Migrations\ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs" />
|
||||
<Compile Include="Strategies\NotificationsHandler.cs" />
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Web;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Models.Segments;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
using Umbraco.Web.Security;
|
||||
using umbraco;
|
||||
using umbraco.BusinessLogic;
|
||||
@@ -315,6 +317,21 @@ namespace Umbraco.Web
|
||||
}
|
||||
}
|
||||
|
||||
private RequestSegments _requestSegments;
|
||||
|
||||
/// <summary>
|
||||
/// Return the current request segments
|
||||
/// </summary>
|
||||
public RequestSegments RequestSegments
|
||||
{
|
||||
get
|
||||
{
|
||||
return _requestSegments ?? (_requestSegments = new RequestSegments(
|
||||
ContentSegmentProviderResolver.Current.Providers,
|
||||
OriginalRequestUrl, CleanedUmbracoUrl, HttpContext.Request));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this
|
||||
/// object is instantiated which in the web site is created during the BeginRequest phase.
|
||||
|
||||
@@ -13,6 +13,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Routing;
|
||||
@@ -147,10 +148,18 @@ namespace Umbraco.Web
|
||||
if (HandleHttpResponseStatus(httpContext, pcr))
|
||||
return;
|
||||
|
||||
if (!pcr.HasPublishedContent)
|
||||
httpContext.RemapHandler(new PublishedContentNotFoundHandler());
|
||||
else
|
||||
RewriteToUmbracoHandler(httpContext, pcr);
|
||||
if (pcr.HasPublishedContent == false)
|
||||
{
|
||||
httpContext.RemapHandler(new PublishedContentNotFoundHandler());
|
||||
}
|
||||
else
|
||||
{
|
||||
//It's a front-end request, we'll make sure the segments are written
|
||||
umbracoContext.RequestSegments.EnsurePersisted(httpContext.Response, httpContext.Request);
|
||||
|
||||
RewriteToUmbracoHandler(httpContext, pcr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -512,6 +521,73 @@ namespace Umbraco.Web
|
||||
urlRouting.PostResolveRequestCache(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the request is a front-end request, check if the member is logged in, then persist the request segments
|
||||
/// to the member.
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
static void PersistMemberSegmentData(HttpContextBase httpContext)
|
||||
{
|
||||
|
||||
if (httpContext.User != null && httpContext.User.Identity.IsAuthenticated
|
||||
&& UmbracoContext.Current != null
|
||||
&& UmbracoContext.Current.IsFrontEndUmbracoRequest
|
||||
&& Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider().IsUmbracoMembershipProvider())
|
||||
{
|
||||
var persisted = UmbracoContext.Current.RequestSegments.PersistedSegments.ToArray();
|
||||
|
||||
if (persisted.Any())
|
||||
{
|
||||
var member = ApplicationContext.Current.Services.MemberService.GetByUsername(
|
||||
httpContext.User.Identity.Name);
|
||||
if (member != null)
|
||||
{
|
||||
var segmentsProp = member.Properties.FirstOrDefault(x => x.Alias == Constants.Conventions.Member.Segments);
|
||||
|
||||
if (segmentsProp == null || segmentsProp.HasIdentity == false)
|
||||
{
|
||||
//we need to create the segments property
|
||||
var memberType = member.ContentType;
|
||||
|
||||
try
|
||||
{
|
||||
if (segmentsProp == null)
|
||||
{
|
||||
//if there is no property, we'll create it, chances are it exists by default with an empty identity
|
||||
memberType.AddPropertyType(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Ntext, true)
|
||||
{
|
||||
Alias = Constants.Conventions.Member.Segments,
|
||||
Name = Constants.Conventions.Member.SegmentsLabel
|
||||
});
|
||||
}
|
||||
|
||||
ApplicationContext.Current.Services.MemberTypeService.Save(memberType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<UmbracoModule>("Could not create the segments property type for the current member", ex);
|
||||
return;
|
||||
}
|
||||
//re-get the member
|
||||
member = ApplicationContext.Current.Services.MemberService.GetByUsername(
|
||||
httpContext.User.Identity.Name);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//save as json
|
||||
member.SetValue(Constants.Conventions.Member.Segments, JsonConvert.SerializeObject(persisted));
|
||||
ApplicationContext.Current.Services.MemberService.Save(member);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<UmbracoModule>("Could not save the segments for the current member", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the xml cache file needs to be updated/persisted
|
||||
/// </summary>
|
||||
@@ -600,8 +676,9 @@ namespace Umbraco.Web
|
||||
// used to check if the xml cache file needs to be updated/persisted
|
||||
app.PostRequestHandlerExecute += (sender, e) =>
|
||||
{
|
||||
var httpContext = ((HttpApplication)sender).Context;
|
||||
PersistXmlCache(new HttpContextWrapper(httpContext));
|
||||
var httpContext = new HttpContextWrapper(((HttpApplication)sender).Context);
|
||||
PersistXmlCache(httpContext);
|
||||
PersistMemberSegmentData(httpContext);
|
||||
};
|
||||
|
||||
app.EndRequest += (sender, args) =>
|
||||
|
||||
@@ -30,6 +30,7 @@ using Umbraco.Web.PropertyEditors;
|
||||
using Umbraco.Web.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Routing.Segments;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.UI.JavaScript;
|
||||
using Umbraco.Web.WebApi;
|
||||
@@ -291,6 +292,9 @@ namespace Umbraco.Web
|
||||
{
|
||||
base.InitializeResolvers();
|
||||
|
||||
ContentSegmentProviderResolver.Current = new ContentSegmentProviderResolver(
|
||||
PluginManager.Current.ResolveTypes<ContentSegmentProvider>());
|
||||
|
||||
XsltExtensionsResolver.Current = new XsltExtensionsResolver(() => PluginManager.Current.ResolveXsltExtensions());
|
||||
|
||||
//set the default RenderMvcController
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -252,7 +253,9 @@ namespace umbraco
|
||||
|
||||
var doctype = xmlDoc.DocumentType;
|
||||
var subset = doctype.InternalSubset;
|
||||
if (!subset.Contains(string.Format("<!ATTLIST {0} id ID #REQUIRED>", docTypeAlias)))
|
||||
|
||||
//do the normal aliases
|
||||
if (subset.Contains(string.Format("<!ATTLIST {0} id ID #REQUIRED>", docTypeAlias)) == false)
|
||||
{
|
||||
subset = string.Format("<!ELEMENT {0} ANY>\r\n<!ATTLIST {0} id ID #REQUIRED>\r\n{1}", docTypeAlias, subset);
|
||||
var xmlDoc2 = new XmlDocument();
|
||||
@@ -264,7 +267,7 @@ namespace umbraco
|
||||
// apply
|
||||
xmlDoc = xmlDoc2;
|
||||
}
|
||||
|
||||
|
||||
return xmlDoc;
|
||||
}
|
||||
|
||||
@@ -381,7 +384,7 @@ namespace umbraco
|
||||
public static XmlDocument AppendDocumentXml(int id, int level, int parentId, XmlNode docNode, XmlDocument xmlContentCopy)
|
||||
{
|
||||
// Find the document in the xml cache
|
||||
XmlNode currentNode = xmlContentCopy.GetElementById(id.ToString());
|
||||
XmlNode currentNode = xmlContentCopy.GetElementById(id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
// if the document is not there already then it's a new document
|
||||
// we must make sure that its document type exists in the schema
|
||||
@@ -396,14 +399,17 @@ namespace umbraco
|
||||
// Find the parent (used for sortering and maybe creation of new node)
|
||||
XmlNode parentNode = level == 1
|
||||
? xmlContentCopy.DocumentElement
|
||||
: xmlContentCopy.GetElementById(parentId.ToString());
|
||||
: xmlContentCopy.GetElementById(parentId.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
var isVariant = docNode.AttributeValue<string>("masterDocId").IsNullOrWhiteSpace() == false;
|
||||
|
||||
if (parentNode != null)
|
||||
{
|
||||
if (currentNode == null)
|
||||
{
|
||||
currentNode = docNode;
|
||||
parentNode.AppendChild(currentNode);
|
||||
|
||||
parentNode.AppendChild(currentNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -412,7 +418,7 @@ namespace umbraco
|
||||
|
||||
//update the node with it's new values
|
||||
TransferValuesFromDocumentXmlToPublishedXml(docNode, currentNode);
|
||||
|
||||
|
||||
//If the node is being moved we also need to ensure that it exists under the new parent!
|
||||
// http://issues.umbraco.org/issue/U4-2312
|
||||
// we were never checking this before and instead simply changing the parentId value but not
|
||||
@@ -531,25 +537,10 @@ namespace umbraco
|
||||
/// Updates the document cache for multiple documents
|
||||
/// </summary>
|
||||
/// <param name="Documents">The documents.</param>
|
||||
[Obsolete("This is not used and will be removed from the codebase in future versions")]
|
||||
[Obsolete("This is not used and will be removed from the codebase in future versions", true)]
|
||||
public virtual void UpdateDocumentCache(List<Document> Documents)
|
||||
{
|
||||
// We need to lock content cache here, because we cannot allow other threads
|
||||
// making changes at the same time, they need to be queued
|
||||
int parentid = Documents[0].Id;
|
||||
|
||||
lock (XmlContentInternalSyncLock)
|
||||
{
|
||||
// Make copy of memory content, we cannot make changes to the same document
|
||||
// the is read from elsewhere
|
||||
XmlDocument xmlContentCopy = CloneXmlDoc(XmlContentInternal);
|
||||
foreach (Document d in Documents)
|
||||
{
|
||||
PublishNodeDo(d, xmlContentCopy, true);
|
||||
}
|
||||
XmlContentInternal = xmlContentCopy;
|
||||
ClearContextCache();
|
||||
}
|
||||
//NOTE: The obsolete attribute with throw a YSOD
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -559,12 +550,7 @@ namespace umbraco
|
||||
[Obsolete("Method obsolete in version 4.1 and later, please use UpdateDocumentCache", true)]
|
||||
public virtual void UpdateDocumentCacheAsync(int documentId)
|
||||
{
|
||||
//SD: WE've obsoleted this but then didn't make it call the method it should! So we've just
|
||||
// left a bug behind...???? ARGH.
|
||||
//.... changed now.
|
||||
//ThreadPool.QueueUserWorkItem(delegate { UpdateDocumentCache(documentId); });
|
||||
|
||||
UpdateDocumentCache(documentId);
|
||||
//NOTE: The obsolete attribute with throw a YSOD
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -574,12 +560,7 @@ namespace umbraco
|
||||
[Obsolete("Method obsolete in version 4.1 and later, please use ClearDocumentCache", true)]
|
||||
public virtual void ClearDocumentCacheAsync(int documentId)
|
||||
{
|
||||
//SD: WE've obsoleted this but then didn't make it call the method it should! So we've just
|
||||
// left a bug behind...???? ARGH.
|
||||
//.... changed now.
|
||||
//ThreadPool.QueueUserWorkItem(delegate { ClearDocumentCache(documentId); });
|
||||
|
||||
ClearDocumentCache(documentId);
|
||||
//NOTE: The obsolete attribute with throw a YSOD
|
||||
}
|
||||
|
||||
public virtual void ClearDocumentCache(int documentId)
|
||||
@@ -607,7 +588,7 @@ namespace umbraco
|
||||
doc.XmlRemoveFromDB();
|
||||
|
||||
// Check if node present, before cloning
|
||||
x = XmlContentInternal.GetElementById(doc.Id.ToString());
|
||||
x = XmlContentInternal.GetElementById(doc.Id.ToString(CultureInfo.InvariantCulture));
|
||||
if (x == null)
|
||||
return;
|
||||
|
||||
@@ -620,7 +601,7 @@ namespace umbraco
|
||||
XmlDocument xmlContentCopy = CloneXmlDoc(XmlContentInternal);
|
||||
|
||||
// Find the document in the xml cache
|
||||
x = xmlContentCopy.GetElementById(doc.Id.ToString());
|
||||
x = xmlContentCopy.GetElementById(doc.Id.ToString(CultureInfo.InvariantCulture));
|
||||
if (x != null)
|
||||
{
|
||||
// The document already exists in cache, so repopulate it
|
||||
@@ -1027,7 +1008,7 @@ namespace umbraco
|
||||
private static void InitContentDocument(XmlDocument xmlDoc, string dtd)
|
||||
{
|
||||
// Prime the xml document with an inline dtd and a root element
|
||||
xmlDoc.LoadXml(String.Format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>{0}{1}{0}<root id=\"-1\"/>",
|
||||
xmlDoc.LoadXml(String.Format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>{0}{1}{0}<root id=\"-1\" />",
|
||||
Environment.NewLine,
|
||||
dtd));
|
||||
}
|
||||
@@ -1176,6 +1157,9 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
//TODO: Here we need to deal with variants
|
||||
|
||||
private static void GenerateXmlDocument(IDictionary<int, List<int>> hierarchy,
|
||||
IDictionary<int, XmlNode> nodeIndex, int parentId, XmlNode parentNode)
|
||||
{
|
||||
@@ -1183,14 +1167,14 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
|
||||
if (hierarchy.TryGetValue(parentId, out children))
|
||||
{
|
||||
XmlNode childContainer = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ||
|
||||
var childContainer = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ||
|
||||
String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME)
|
||||
? parentNode
|
||||
: parentNode.SelectSingleNode(
|
||||
UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME);
|
||||
|
||||
if (!UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema &&
|
||||
!String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME))
|
||||
if (UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema == false &&
|
||||
String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME) == false)
|
||||
{
|
||||
if (childContainer == null)
|
||||
{
|
||||
@@ -1203,8 +1187,8 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
|
||||
foreach (int childId in children)
|
||||
{
|
||||
XmlNode childNode = nodeIndex[childId];
|
||||
|
||||
var childNode = nodeIndex[childId];
|
||||
|
||||
if (UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ||
|
||||
String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME))
|
||||
{
|
||||
|
||||
@@ -1291,7 +1291,7 @@ namespace umbraco
|
||||
var nav = contentNavigator.Clone();
|
||||
if (nav.MoveToId(contentId))
|
||||
{
|
||||
var descendantIterator = nav.Select("./* [@isDoc]");
|
||||
var descendantIterator = nav.Select("./* [@isDoc='']");
|
||||
if (descendantIterator.MoveNext())
|
||||
{
|
||||
// not empty - and won't change
|
||||
|
||||
@@ -148,7 +148,8 @@ namespace umbraco
|
||||
}
|
||||
else
|
||||
{
|
||||
return uQuery.GetNodesByXPath(string.Concat("descendant::", documentTypeAlias, "[@isDoc]"));
|
||||
//variants have isDoc='variant' so we don't want those
|
||||
return uQuery.GetNodesByXPath(string.Concat("descendant::", documentTypeAlias, "[@isDoc='']"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@@ -14,6 +15,7 @@ using umbraco.businesslogic.Exceptions;
|
||||
namespace umbraco.IO
|
||||
{
|
||||
[Obsolete("Use Umbraco.Core.IO.IOHelper instead")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class IOHelper
|
||||
{
|
||||
public static char DirSepChar
|
||||
|
||||
@@ -402,7 +402,7 @@ namespace umbraco.cms.businesslogic
|
||||
public virtual void XmlPopulate(XmlDocument xd, ref XmlNode x, bool Deep)
|
||||
{
|
||||
var props = this.GenericProperties;
|
||||
foreach (property.Property p in props)
|
||||
foreach (var p in props)
|
||||
if (p != null && p.Value != null && string.IsNullOrEmpty(p.Value.ToString()) == false)
|
||||
x.AppendChild(p.ToXml(xd));
|
||||
|
||||
@@ -534,7 +534,7 @@ namespace umbraco.cms.businesslogic
|
||||
SqlHelper.CreateParameter("@updateDate", versionDate));
|
||||
|
||||
List<PropertyType> pts = ContentType.PropertyTypes;
|
||||
foreach (propertytype.PropertyType pt in pts)
|
||||
foreach (var pt in pts)
|
||||
{
|
||||
object oldValue = "";
|
||||
if (tempHasVersion)
|
||||
@@ -545,7 +545,7 @@ namespace umbraco.cms.businesslogic
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
property.Property p = this.addProperty(pt, newVersion);
|
||||
var p = this.addProperty(pt, newVersion);
|
||||
if (oldValue != null && oldValue.ToString() != "") p.Value = oldValue;
|
||||
}
|
||||
this.Version = newVersion;
|
||||
@@ -554,8 +554,8 @@ namespace umbraco.cms.businesslogic
|
||||
|
||||
protected virtual XmlNode generateXmlWithoutSaving(XmlDocument xd)
|
||||
{
|
||||
string nodeName = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "node" : Casing.SafeAliasWithForcingCheck(ContentType.Alias);
|
||||
XmlNode x = xd.CreateNode(XmlNodeType.Element, nodeName, "");
|
||||
var nodeName = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "node" : Casing.SafeAliasWithForcingCheck(ContentType.Alias);
|
||||
var x = xd.CreateNode(XmlNodeType.Element, nodeName, "");
|
||||
XmlPopulate(xd, ref x, false);
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -1230,7 +1231,7 @@ namespace umbraco.cms.businesslogic.web
|
||||
{
|
||||
XmlNode x = generateXmlWithoutSaving(xd);
|
||||
// Save to db
|
||||
saveXml(x);
|
||||
SaveXml(x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1248,13 +1249,13 @@ namespace umbraco.cms.businesslogic.web
|
||||
if (_xml == null)
|
||||
{
|
||||
// Load xml from db if _xml hasn't been loaded yet
|
||||
_xml = importXml();
|
||||
_xml = ImportXml();
|
||||
|
||||
// Generate xml if xml still null (then it hasn't been initialized before)
|
||||
if (_xml == null)
|
||||
{
|
||||
XmlGenerate(new XmlDocument());
|
||||
_xml = importXml();
|
||||
_xml = ImportXml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1284,57 +1285,10 @@ namespace umbraco.cms.businesslogic.web
|
||||
/// <param name="Deep">If true the documents childrens xmlrepresentation will be appended to the Xmlnode recursive</param>
|
||||
public override void XmlPopulate(XmlDocument xd, ref XmlNode x, bool Deep)
|
||||
{
|
||||
string urlName = this.Content.GetUrlSegment().ToLower();
|
||||
foreach (Property p in GenericProperties.Where(p => p != null && p.Value != null && string.IsNullOrEmpty(p.Value.ToString()) == false))
|
||||
x.AppendChild(p.ToXml(xd));
|
||||
var resultXml = ApplicationContext.Current.Services.PackagingService.Export(Content, Deep);
|
||||
|
||||
// attributes
|
||||
x.Attributes.Append(addAttribute(xd, "id", Id.ToString()));
|
||||
// x.Attributes.Append(addAttribute(xd, "version", Version.ToString()));
|
||||
if (Level > 1)
|
||||
x.Attributes.Append(addAttribute(xd, "parentID", Parent.Id.ToString()));
|
||||
else
|
||||
x.Attributes.Append(addAttribute(xd, "parentID", "-1"));
|
||||
x.Attributes.Append(addAttribute(xd, "level", Level.ToString()));
|
||||
x.Attributes.Append(addAttribute(xd, "writerID", Writer.Id.ToString()));
|
||||
x.Attributes.Append(addAttribute(xd, "creatorID", Creator.Id.ToString()));
|
||||
if (ContentType != null)
|
||||
x.Attributes.Append(addAttribute(xd, "nodeType", ContentType.Id.ToString()));
|
||||
x.Attributes.Append(addAttribute(xd, "template", _template.ToString()));
|
||||
x.Attributes.Append(addAttribute(xd, "sortOrder", sortOrder.ToString()));
|
||||
x.Attributes.Append(addAttribute(xd, "createDate", CreateDateTime.ToString("s")));
|
||||
x.Attributes.Append(addAttribute(xd, "updateDate", VersionDate.ToString("s")));
|
||||
x.Attributes.Append(addAttribute(xd, "nodeName", Text));
|
||||
x.Attributes.Append(addAttribute(xd, "urlName", urlName));
|
||||
x.Attributes.Append(addAttribute(xd, "writerName", Writer.Name));
|
||||
x.Attributes.Append(addAttribute(xd, "creatorName", Creator.Name.ToString()));
|
||||
if (ContentType != null && UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema)
|
||||
x.Attributes.Append(addAttribute(xd, "nodeTypeAlias", ContentType.Alias));
|
||||
x.Attributes.Append(addAttribute(xd, "path", Path));
|
||||
|
||||
if (!UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema)
|
||||
{
|
||||
x.Attributes.Append(addAttribute(xd, "isDoc", ""));
|
||||
}
|
||||
|
||||
if (Deep)
|
||||
{
|
||||
//store children array here because iterating over an Array object is very inneficient.
|
||||
var c = Children;
|
||||
foreach (Document d in c)
|
||||
{
|
||||
XmlNode xml = d.ToXml(xd, true);
|
||||
if (xml != null)
|
||||
{
|
||||
x.AppendChild(xml);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Debug<Document>(string.Format("Document {0} not published so XML cannot be generated", d.Id));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//convert it from the result
|
||||
x = resultXml.ToXmlNode(xd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1347,19 +1301,19 @@ namespace umbraco.cms.businesslogic.web
|
||||
{
|
||||
if (_xml == null)
|
||||
// Load xml from db if _xml hasn't been loaded yet
|
||||
_xml = importXml();
|
||||
_xml = ImportXml();
|
||||
|
||||
// Generate xml if xml still null (then it hasn't been initialized before)
|
||||
if (_xml == null)
|
||||
{
|
||||
XmlGenerate(new XmlDocument());
|
||||
_xml = importXml();
|
||||
_xml = ImportXml();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the sort order attr
|
||||
_xml.Attributes.GetNamedItem("sortOrder").Value = sortOrder.ToString();
|
||||
saveXml(_xml);
|
||||
SaveXml(_xml);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1529,10 +1483,10 @@ namespace umbraco.cms.businesslogic.web
|
||||
VersionDate = versionDate;
|
||||
}
|
||||
|
||||
private XmlAttribute addAttribute(XmlDocument Xd, string Name, string Value)
|
||||
private XmlAttribute AddAttribute(XmlDocument xd, string name, string value)
|
||||
{
|
||||
XmlAttribute temp = Xd.CreateAttribute(Name);
|
||||
temp.Value = Value;
|
||||
XmlAttribute temp = xd.CreateAttribute(name);
|
||||
temp.Value = value;
|
||||
return temp;
|
||||
}
|
||||
|
||||
@@ -1541,7 +1495,7 @@ namespace umbraco.cms.businesslogic.web
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
private void saveXml(XmlNode x)
|
||||
private void SaveXml(XmlNode x)
|
||||
{
|
||||
bool exists = (SqlHelper.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsContentXml WHERE nodeId=@nodeId",
|
||||
SqlHelper.CreateParameter("@nodeId", Id)) != 0);
|
||||
@@ -1552,7 +1506,7 @@ namespace umbraco.cms.businesslogic.web
|
||||
SqlHelper.CreateParameter("@xml", x.OuterXml));
|
||||
}
|
||||
|
||||
private XmlNode importXml()
|
||||
private XmlNode ImportXml()
|
||||
{
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
XmlReader xmlRdr = SqlHelper.ExecuteXmlReader(string.Format(
|
||||
|
||||
@@ -108,6 +108,7 @@ namespace umbraco.cms.businesslogic.web
|
||||
{
|
||||
strictSchemaBuilder.AppendLine(String.Format("<!ELEMENT {0} ANY>", safeAlias));
|
||||
strictSchemaBuilder.AppendLine(String.Format("<!ATTLIST {0} id ID #REQUIRED>", safeAlias));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user