using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Web; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { /// /// Represents an abstract class for base Content properties and methods /// [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] public abstract class ContentBase : Entity, IContentBase { protected IContentTypeComposition ContentTypeBase; private Lazy _parentId; private string _name;//NOTE Once localization is introduced this will be the localized Name of the Content/Media. private int _sortOrder; private int _level; private string _path; private int _creatorId; private bool _trashed; private int _contentTypeId; private PropertyCollection _properties; private readonly List _lastInvalidProperties = new List(); /// /// Protected constructor for ContentBase (Base for Content and Media) /// /// Localized Name of the entity /// /// /// protected ContentBase(string name, int parentId, IContentTypeComposition contentType, PropertyCollection properties) { Mandate.ParameterCondition(parentId != 0, "parentId"); Mandate.ParameterNotNull(contentType, "contentType"); Mandate.ParameterNotNull(properties, "properties"); ContentTypeBase = contentType; Version = Guid.NewGuid(); _parentId = new Lazy(() => parentId); _name = name; _contentTypeId = contentType.Id; _properties = properties; _properties.EnsurePropertyTypes(PropertyTypes); _additionalData = new Dictionary(); } /// /// Protected constructor for ContentBase (Base for Content and Media) /// /// Localized Name of the entity /// /// /// protected ContentBase(string name, IContentBase parent, IContentTypeComposition contentType, PropertyCollection properties) { Mandate.ParameterNotNull(parent, "parent"); Mandate.ParameterNotNull(contentType, "contentType"); Mandate.ParameterNotNull(properties, "properties"); ContentTypeBase = contentType; Version = Guid.NewGuid(); _parentId = new Lazy(() => parent.Id); _name = name; _contentTypeId = contentType.Id; _properties = properties; _properties.EnsurePropertyTypes(PropertyTypes); _additionalData = new Dictionary(); } private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); private static readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); private readonly static PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) { OnPropertyChanged(PropertyCollectionSelector); } /// /// Gets or sets the Id of the Parent entity /// [DataMember] public virtual int ParentId { get { var val = _parentId.Value; if (val == 0) { throw new InvalidOperationException("The ParentId cannot have a value of 0. Perhaps the parent object used to instantiate this object has not been persisted to the data store."); } return val; } set { _parentId = new Lazy(() => value); OnPropertyChanged(ParentIdSelector); } } /// /// Gets or sets the name of the entity /// [DataMember] public virtual string Name { get { return _name; } set { SetPropertyValueAndDetectChanges(o => { _name = value; return _name; }, _name, NameSelector); } } /// /// Gets or sets the sort order of the content entity /// [DataMember] public virtual int SortOrder { get { return _sortOrder; } set { SetPropertyValueAndDetectChanges(o => { _sortOrder = value; return _sortOrder; }, _sortOrder, SortOrderSelector); } } /// /// Gets or sets the level of the content entity /// [DataMember] public virtual int Level { get { return _level; } set { SetPropertyValueAndDetectChanges(o => { _level = value; return _level; }, _level, LevelSelector); } } /// /// Gets or sets the path /// [DataMember] public virtual string Path //Setting this value should be handled by the class not the user { get { return _path; } set { SetPropertyValueAndDetectChanges(o => { _path = value; return _path; }, _path, PathSelector); } } /// /// Profile of the user who created this Content /// [DataMember] public virtual int CreatorId { get { return _creatorId; } set { SetPropertyValueAndDetectChanges(o => { _creatorId = value; return _creatorId; }, _creatorId, CreatorIdSelector); } } /// /// Boolean indicating whether this Content is Trashed or not. /// If Content is Trashed it will be located in the Recyclebin. /// /// When content is trashed it should be unpublished [DataMember] public virtual bool Trashed //Setting this value should be handled by the class not the user { get { return _trashed; } internal set { SetPropertyValueAndDetectChanges(o => { _trashed = value; return _trashed; }, _trashed, TrashedSelector); } } /// /// Guid Id of the curent Version /// [DataMember] public Guid Version { get; internal set; } /// /// Integer Id of the default ContentType /// [DataMember] public virtual int ContentTypeId { get { //There will be cases where this has not been updated to reflect the true content type ID. //This will occur when inserting new content. if (_contentTypeId == 0 && ContentTypeBase != null && ContentTypeBase.HasIdentity) { _contentTypeId = ContentTypeBase.Id; } return _contentTypeId; } protected set { SetPropertyValueAndDetectChanges(o => { _contentTypeId = value; return _contentTypeId; }, _contentTypeId, DefaultContentTypeIdSelector); } } /// /// Collection of properties, which make up all the data available for this Content object /// [DataMember] public virtual PropertyCollection Properties { get { return _properties; } set { _properties = value; _properties.CollectionChanged += PropertiesChanged; } } private readonly IDictionary _additionalData; /// /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// [EditorBrowsable(EditorBrowsableState.Never)] IDictionary IUmbracoEntity.AdditionalData { get { return _additionalData; } } /// /// List of PropertyGroups available on this Content object /// [IgnoreDataMember] public IEnumerable PropertyGroups { get { return ContentTypeBase.CompositionPropertyGroups; } } /// /// List of PropertyTypes available on this Content object /// [IgnoreDataMember] public IEnumerable PropertyTypes { get { return ContentTypeBase.CompositionPropertyTypes; } } /// /// Indicates whether the content object has a property with the supplied alias /// /// Alias of the PropertyType /// True if Property with given alias exists, otherwise False public virtual bool HasProperty(string propertyTypeAlias) { return Properties.Contains(propertyTypeAlias); } /// /// Gets the value of a Property /// /// Alias of the PropertyType /// Value as an public virtual object GetValue(string propertyTypeAlias) { return Properties[propertyTypeAlias].Value; } /// /// Gets the value of a Property /// /// Type of the value to return /// Alias of the PropertyType /// Value as a public virtual TPassType GetValue(string propertyTypeAlias) { var convertAttempt = Properties[propertyTypeAlias].Value.TryConvertTo(); return convertAttempt.Success ? convertAttempt.Result : default(TPassType); } /// /// Sets the value of a Property /// /// Alias of the PropertyType /// Value to set for the Property public virtual void SetValue(string propertyTypeAlias, object value) { if (value == null) { SetValueOnProperty(propertyTypeAlias, value); return; } // .NET magic to call one of the 'SetPropertyValue' handlers with matching signature ((dynamic)this).SetPropertyValue(propertyTypeAlias, (dynamic)value); } /// /// Sets the value of a Property /// /// Alias of the PropertyType /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, string value) { SetValueOnProperty(propertyTypeAlias, value); } /// /// Sets the value of a Property /// /// Alias of the PropertyType /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, int value) { SetValueOnProperty(propertyTypeAlias, value); } /// /// Sets the value of a Property /// /// Alias of the PropertyType /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, long value) { string val = value.ToString(); SetValueOnProperty(propertyTypeAlias, val); } /// /// Sets the value of a Property /// /// Alias of the PropertyType /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, bool value) { int val = Convert.ToInt32(value); SetValueOnProperty(propertyTypeAlias, val); } /// /// Sets the value of a Property /// /// Alias of the PropertyType /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, DateTime value) { SetValueOnProperty(propertyTypeAlias, value); } /// /// Sets the value of a Property /// /// Alias of the PropertyType /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, HttpPostedFile value) { ContentExtensions.SetValue(this, propertyTypeAlias, value); } /// /// Sets the value of a Property /// /// Alias of the PropertyType /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, HttpPostedFileBase value) { ContentExtensions.SetValue(this, propertyTypeAlias, value); } /// /// Sets the value of a Property /// /// Alias of the PropertyType /// Value to set for the Property [Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")] public virtual void SetPropertyValue(string propertyTypeAlias, HttpPostedFileWrapper value) { ContentExtensions.SetValue(this, propertyTypeAlias, value); } /// /// Private method to set the value of a property /// /// /// private void SetValueOnProperty(string propertyTypeAlias, object value) { if (Properties.Contains(propertyTypeAlias)) { Properties[propertyTypeAlias].Value = value; return; } var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); if (propertyType == null) { throw new Exception(String.Format("No PropertyType exists with the supplied alias: {0}", propertyTypeAlias)); } Properties.Add(propertyType.CreatePropertyFromValue(value)); } /// /// Boolean indicating whether the content and its properties are valid /// /// True if content is valid otherwise false public virtual bool IsValid() { _lastInvalidProperties.Clear(); _lastInvalidProperties.AddRange(Properties.Where(property => property.IsValid() == false)); return _lastInvalidProperties.Any() == false; } /// /// Returns a collection of the result of the last validation process, this collection contains all invalid properties. /// [IgnoreDataMember] internal IEnumerable LastInvalidProperties { get { return _lastInvalidProperties; } } public abstract void ChangeTrashedState(bool isTrashed, int parentId = -20); /// /// We will override this method to ensure that when we reset the dirty properties that we /// also reset the dirty changes made to the content's Properties (user defined) /// /// public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) { base.ResetDirtyProperties(rememberPreviouslyChangedProperties); foreach (var prop in Properties) { prop.ResetDirtyProperties(rememberPreviouslyChangedProperties); } } } }