using System; using System.Collections.Generic; using System.Diagnostics; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Models { /// /// Provide an abstract base class for IPublishedContent implementations. /// /// This base class does which (a) consitently resolves and caches the Url, (b) provides an implementation /// for this[alias], and (c) provides basic content set management. [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] public abstract class PublishedContentBase : IPublishedContent { #region Content private string _url; /// /// Gets the url of the content. /// /// /// If this content is Content, the url that is returned is the one computed by the NiceUrlProvider, otherwise if /// this content is Media, the url returned is the value found in the 'umbracoFile' property. /// public virtual string Url { get { // should be thread-safe although it won't prevent url from being resolved more than once if (_url != null) return _url; switch (ItemType) { case PublishedItemType.Content: if (UmbracoContext.Current == null) throw new InvalidOperationException( "Cannot resolve a Url for a content item when UmbracoContext.Current is null."); if (UmbracoContext.Current.UrlProvider == null) throw new InvalidOperationException( "Cannot resolve a Url for a content item when UmbracoContext.Current.UrlProvider is null."); _url = UmbracoContext.Current.UrlProvider.GetUrl(Id); break; case PublishedItemType.Media: var prop = GetProperty(Constants.Conventions.Media.File); if (prop == null || prop.Value == null) { _url = string.Empty; return _url; } var propType = ContentType.GetPropertyType(Constants.Conventions.Media.File); //This is a hack - since we now have 2 properties that support a URL: upload and cropper, we need to detect this since we always // want to return the normal URL and the cropper stores data as json switch (propType.PropertyEditorAlias) { case Constants.PropertyEditors.UploadFieldAlias: _url = prop.Value.ToString(); break; case Constants.PropertyEditors.ImageCropperAlias: //get the url from the json format var val = prop.Value.ToString(); var crops = val.SerializeToCropDataSet(); _url = crops != null ? crops.Src : string.Empty; break; } break; default: throw new NotSupportedException(); } return _url; } } public abstract PublishedItemType ItemType { get; } public abstract int Id { get; } public abstract int TemplateId { get; } public abstract int SortOrder { get; } public abstract string Name { get; } public abstract string UrlName { get; } public abstract string DocumentTypeAlias { get; } public abstract int DocumentTypeId { get; } public abstract string WriterName { get; } public abstract string CreatorName { get; } public abstract int WriterId { get; } public abstract int CreatorId { get; } public abstract string Path { get; } public abstract DateTime CreateDate { get; } public abstract DateTime UpdateDate { get; } public abstract Guid Version { get; } public abstract int Level { get; } public abstract bool IsDraft { get; } public int GetIndex() { var index = this.Siblings().FindIndex(x => x.Id == Id); if (index < 0) throw new IndexOutOfRangeException("Could not find content in the content set."); return index; } #endregion #region Tree /// /// Gets the parent of the content. /// public abstract IPublishedContent Parent { get; } /// /// Gets the children of the content. /// /// Children are sorted by their sortOrder. public abstract IEnumerable Children { get; } #endregion #region ContentSet public virtual IEnumerable ContentSet { // the default content set of a content is its siblings get { return this.Siblings(); } } #endregion #region ContentType public abstract PublishedContentType ContentType { get; } #endregion #region Properties /// /// Gets the properties of the content. /// public abstract ICollection Properties { get; } /// /// Gets the value of a property identified by its alias. /// /// The property alias. /// The value of the property identified by the alias. /// /// If GetProperty(alias) is null then returns null else return GetProperty(alias).Value. /// So if the property has no value, returns the default value for that property type. /// This one is defined here really because we cannot define index extension methods, but all it should do is: /// var p = GetProperty(alias); return p == null ? null : p.Value; and nothing else. /// The recursive syntax (eg "_title") is _not_ supported here. /// The alias is case-insensitive. /// public virtual object this[string alias] { get { // no cache here: GetProperty should be fast, and .Value cache should be managed by the property. var property = GetProperty(alias); return property == null ? null : property.Value; } } /// /// Gets a property identified by its alias. /// /// The property alias. /// The property identified by the alias. /// /// If no property with the specified alias exists, returns null. /// The returned property may have no value (ie HasValue is false). /// The alias is case-insensitive. /// public abstract IPublishedProperty GetProperty(string alias); /// /// Gets a property identified by its alias. /// /// The property alias. /// A value indicating whether to navigate the tree upwards until a property with a value is found. /// The property identified by the alias. /// /// Navigate the tree upwards and look for a property with that alias and with a value (ie HasValue is true). /// If found, return the property. If no property with that alias is found, having a value or not, return null. Otherwise /// return the first property that was found with the alias but had no value (ie HasValue is false). /// The alias is case-insensitive. /// public virtual IPublishedProperty GetProperty(string alias, bool recurse) { var property = GetProperty(alias); if (recurse == false) return property; IPublishedContent content = this; var firstNonNullProperty = property; while (content != null && (property == null || property.HasValue == false)) { content = content.Parent; property = content == null ? null : content.GetProperty(alias); if (firstNonNullProperty == null && property != null) firstNonNullProperty = property; } // if we find a content with the property with a value, return that property // if we find no content with the property, return null // if we find a content with the property without a value, return that property // have to save that first property while we look further up, hence firstNonNullProperty return property != null && property.HasValue ? property : firstNonNullProperty; } #endregion } }