diff --git a/src/Umbraco.Core/CacheRefreshersResolver.cs b/src/Umbraco.Core/CacheRefreshersResolver.cs
index e01dfba01a..e8df98fc1b 100644
--- a/src/Umbraco.Core/CacheRefreshersResolver.cs
+++ b/src/Umbraco.Core/CacheRefreshersResolver.cs
@@ -27,7 +27,7 @@ namespace Umbraco.Core
///
/// Gets the implementations.
///
- public IEnumerable CacheResolvers
+ public IEnumerable CacheRefreshers
{
get
{
diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs
index 6822582576..7abffc913d 100644
--- a/src/Umbraco.Core/Models/UmbracoEntity.cs
+++ b/src/Umbraco.Core/Models/UmbracoEntity.cs
@@ -54,7 +54,8 @@ namespace Umbraco.Core.Models
Trashed = trashed;
}
- public UmbracoEntity(int trashed)
+ // for MySql
+ public UmbracoEntity(UInt64 trashed)
{
AdditionalData = new Dictionary();
Trashed = trashed == 1;
diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs
index 07f262c272..508e356f71 100644
--- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs
+++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs
@@ -115,6 +115,11 @@ namespace Umbraco.Core.Strings
return InvalidFileNameChars.Contains(c) == false;
}
+ public static string CutMaxLength(string text, int length)
+ {
+ return text.Length <= length ? text : text.Substring(0, length);
+ }
+
#endregion
#region Configuration
@@ -168,6 +173,7 @@ namespace Umbraco.Core.Strings
return WithConfig(CleanStringType.UrlSegment, new Config
{
PreFilter = ApplyUrlReplaceCharacters,
+ PostFilter = x => CutMaxLength(x, 240),
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = (UrlReplacingToAscii ? CleanStringType.Ascii : CleanStringType.Utf8) | CleanStringType.LowerCase,
BreakTermsOnUpper = false,
@@ -202,6 +208,7 @@ namespace Umbraco.Core.Strings
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged;
PreFilter = null;
+ PostFilter = null;
IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c);
BreakTermsOnUpper = false;
CutAcronymOnNonUpper = false;
@@ -214,6 +221,7 @@ namespace Umbraco.Core.Strings
return new Config
{
PreFilter = PreFilter,
+ PostFilter = PostFilter,
IsTerm = IsTerm,
StringType = StringType,
BreakTermsOnUpper = BreakTermsOnUpper,
@@ -224,6 +232,7 @@ namespace Umbraco.Core.Strings
}
public Func PreFilter { get; set; }
+ public Func PostFilter { get; set; }
public Func IsTerm { get; set; }
public CleanStringType StringType { get; set; }
@@ -554,6 +563,10 @@ function validateSafeAlias(id, value, immediate, callback) {{
// clean
text = CleanCodeString(text, stringType, separator.Value, culture, config);
+ // apply post-filter
+ if (config.PostFilter != null)
+ text = config.PostFilter(text);
+
return text;
}
diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs
index 3f3fa0a926..be357c4c03 100644
--- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs
+++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs
@@ -99,6 +99,12 @@ namespace Umbraco.Tests.PublishedContent
{
return _content.Count > 0;
}
+
+ public IPublishedContent CreateFragment(string contentTypeAlias, IDictionary dataValues,
+ bool isPreviewing, bool managed)
+ {
+ throw new NotImplementedException();
+ }
}
class SolidPublishedContent : IPublishedContent
diff --git a/src/Umbraco.Web.UI/umbraco/developer/DataTypes/editDatatype.aspx b/src/Umbraco.Web.UI/umbraco/developer/DataTypes/editDatatype.aspx
index 86066a5c63..12f830e9eb 100644
--- a/src/Umbraco.Web.UI/umbraco/developer/DataTypes/editDatatype.aspx
+++ b/src/Umbraco.Web.UI/umbraco/developer/DataTypes/editDatatype.aspx
@@ -1,4 +1,5 @@
<%@ Page Language="c#" MasterPageFile="../../masterpages/umbracoPage.Master" Title="Edit data type"
+ ValidateRequest="false"
CodeBehind="editDatatype.aspx.cs" AutoEventWireup="True" Inherits="umbraco.cms.presentation.developer.editDatatype" %>
<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs
index 373e103a56..b7220b7cc1 100644
--- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs
+++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs
@@ -141,6 +141,7 @@ namespace Umbraco.Web.Mvc
// maps model
protected override void SetViewData(ViewDataDictionary viewData)
{
+ // if view data contains no model, nothing to do
var source = viewData.Model;
if (source == null)
{
@@ -148,6 +149,8 @@ namespace Umbraco.Web.Mvc
return;
}
+ // get the type of the view data model (what we have)
+ // get the type of this view model (what we want)
var sourceType = source.GetType();
var targetType = typeof (TModel);
@@ -160,13 +163,15 @@ namespace Umbraco.Web.Mvc
// try to grab the content
// if no content is found, return, nothing we can do
- var sourceContent = source as IPublishedContent;
+ var sourceContent = source as IPublishedContent; // check if what we have is an IPublishedContent
if (sourceContent == null && sourceType.Implements())
{
+ // else check if it's an IRenderModel => get the content
sourceContent = ((IRenderModel)source).Content;
}
if (sourceContent == null)
{
+ // else check if we can convert it to a content
var attempt = source.TryConvertTo();
if (attempt.Success) sourceContent = attempt.Result;
}
diff --git a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs
index a80aaa41b3..f020dc6097 100644
--- a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs
+++ b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs
@@ -33,5 +33,53 @@ namespace Umbraco.Web.PublishedCache
/// The route.
/// The value of overrides the context.
string GetRouteById(UmbracoContext umbracoContext, bool preview, int contentId);
+
+ ///
+ /// Creates a content fragment.
+ ///
+ /// The content type alias.
+ /// The content property raw values.
+ /// A value indicating whether the fragment is previewing.
+ /// A value indicating whether the fragment is managed by the cache.
+ /// The newly created content fragment.
+ //
+ // notes
+ //
+ // in XmlPublishedCache, IPublishedContent instances are not meant to survive longer
+ // that a request or else we cannot guarantee that the converted property values will
+ // be properly managed - because XmlPublishedProperty just stores the result of the
+ // conversion locally.
+ //
+ // in DrippingPublishedCache, IPublishedContent instances are meant to survive for as
+ // long as the content itself has not been modified, and the property respects the
+ // converter's indication ie whether the converted value should be cached at
+ // .Content - cache until the content changes
+ // .ContentCache - cache until any content changes
+ // .Request - cache for the current request
+ //
+ // a fragment can be either "detached" or "managed".
+ // detached: created from code, managed by code, converted property values are
+ // cached within the fragment itself for as long as the fragment lives
+ // managed: created from a property converter as part of a content, managed by
+ // the cache, converted property values can be cached...
+ //
+ // XmlPublishedCache: same as content properties, store the result of the
+ // conversion locally, neither content nor fragments should survive longer
+ // than a request
+ // DrippingPublishedCache: depends
+ // .Content: cache within the fragment
+ // .ContentCache, .Request: cache within the cache
+ //
+ // in the latter case, use a fragment-owned guid as the cache key. because we
+ // don't really have any other choice. this opens potential memory leaks: if the
+ // fragment is re-created on each request and has a property that caches its
+ // converted value at .ContentCache level then we'll flood that cache with data
+ // that's never removed (as long as no content is edited).
+ //
+ // so a requirement should be that any converter that creates fragment, should
+ // be marked .Content -- and nothing else
+ //
+ IPublishedContent CreateFragment(string contentTypeAlias, IDictionary dataValues,
+ bool isPreviewing, bool managed);
}
}
diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs
index 82a5b6f85a..2fe9c13c21 100644
--- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs
+++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs
@@ -460,5 +460,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
#endregion
+
+ #region Fragments
+
+ public IPublishedContent CreateFragment(string contentTypeAlias, IDictionary dataValues,
+ bool isPreviewing, bool managed)
+ {
+ return new PublishedFragment(contentTypeAlias, dataValues, isPreviewing, managed);
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs
new file mode 100644
index 0000000000..860f9f043f
--- /dev/null
+++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Web.Models;
+
+namespace Umbraco.Web.PublishedCache.XmlPublishedCache
+{
+ class PublishedFragment : PublishedContentBase
+ {
+ private readonly PublishedContentType _contentType;
+ private readonly IPublishedProperty[] _properties;
+
+ public PublishedFragment(string contentTypeAlias, IDictionary dataValues,
+ bool isPreviewing, bool managed)
+ {
+ IsPreviewing = isPreviewing;
+ _contentType = PublishedContentType.Get(PublishedItemType.Content, contentTypeAlias);
+
+ // we don't care about managed because in both cases, XmlPublishedCache stores
+ // converted property values in the IPublishedContent, which is not meant to
+ // survive the request
+
+ var dataValues2 = new Dictionary();
+ foreach (var kvp in dataValues)
+ dataValues2[kvp.Key.ToLowerInvariant()] = kvp.Value;
+
+ _properties = _contentType.PropertyTypes
+ .Select(x =>
+ {
+ object dataValue;
+ return dataValues2.TryGetValue(x.PropertyTypeAlias.ToLowerInvariant(), out dataValue)
+ ? new PublishedFragmentProperty(x, this, dataValue)
+ : new PublishedFragmentProperty(x, this);
+ })
+ .Cast()
+ .ToArray();
+ }
+
+ #region IPublishedContent
+
+ public override int Id
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override int DocumentTypeId
+ {
+ get { return _contentType.Id; }
+ }
+
+ public override string DocumentTypeAlias
+ {
+ get { return _contentType.Alias; }
+ }
+
+ public override PublishedItemType ItemType
+ {
+ get { return PublishedItemType.Content; }
+ }
+
+ public override string Name
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override int Level
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override string Path
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override int SortOrder
+ {
+ // note - could a published fragment have a sort order?
+ get { throw new NotImplementedException(); }
+ }
+
+ public override Guid Version
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override int TemplateId
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override string UrlName
+ {
+ get { return string.Empty; }
+ }
+
+ public override DateTime CreateDate
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override DateTime UpdateDate
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override int CreatorId
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override string CreatorName
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override int WriterId
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override string WriterName
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override bool IsDraft
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override IPublishedContent Parent
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override IEnumerable Children
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override ICollection Properties
+ {
+ get { return _properties; }
+ }
+
+ public override IPublishedProperty GetProperty(string alias)
+ {
+ return _properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias));
+ }
+
+ public override PublishedContentType ContentType
+ {
+ get { return _contentType; }
+ }
+
+ #endregion
+
+ #region Internal
+
+ // used by PublishedFragmentProperty
+ internal bool IsPreviewing { get; private set; }
+
+ #endregion
+ }
+}
diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs
new file mode 100644
index 0000000000..7ae10aebdd
--- /dev/null
+++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs
@@ -0,0 +1,43 @@
+using System;
+using Umbraco.Core.Models.PublishedContent;
+
+namespace Umbraco.Web.PublishedCache.XmlPublishedCache
+{
+ class PublishedFragmentProperty : PublishedPropertyBase
+ {
+ private readonly object _dataValue;
+ private readonly PublishedFragment _content;
+
+ private readonly Lazy