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 Lazy Ps = new Lazy(); private class PropertySelectors { public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); } protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) { OnPropertyChanged(Ps.Value.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(Ps.Value.ParentIdSelector); } } /// /// Gets or sets the name of the entity /// [DataMember] public virtual string Name { get { return _name; } set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } } /// /// Gets or sets the sort order of the content entity /// [DataMember] public virtual int SortOrder { get { return _sortOrder; } set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } } /// /// Gets or sets the level of the content entity /// [DataMember] public virtual int Level { get { return _level; } set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.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(value, ref _path, Ps.Value.PathSelector); } } /// /// Profile of the user who created this Content /// [DataMember] public virtual int CreatorId { get { return _creatorId; } set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.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(value, ref _trashed, Ps.Value.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(value, ref _contentTypeId, Ps.Value.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) { 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, decimal 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, double 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, 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.InvariantEquals(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); #region Dirty property handling /// /// 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); } } /// /// Indicates whether the current entity is dirty. /// /// True if entity is dirty, otherwise False public override bool IsDirty() { return IsEntityDirty() || this.IsAnyUserPropertyDirty(); } /// /// Indicates whether the current entity was dirty. /// /// True if entity was dirty, otherwise False public override bool WasDirty() { return WasEntityDirty() || this.WasAnyUserPropertyDirty(); } /// /// Returns true if only the entity properties are dirty /// /// public bool IsEntityDirty() { return base.IsDirty(); } /// /// Returns true if only the entity properties were dirty /// /// public bool WasEntityDirty() { return base.WasDirty(); } /// /// Indicates whether a specific property on the current entity is dirty. /// /// Name of the property to check /// /// True if any of the class properties are dirty or /// True if any of the user defined PropertyType properties are dirty based on their alias, /// otherwise False /// public override bool IsPropertyDirty(string propertyName) { bool existsInEntity = base.IsPropertyDirty(propertyName); if (existsInEntity) return true; if (Properties.Contains(propertyName)) { return Properties[propertyName].IsDirty(); } return false; } /// /// Indicates whether a specific property on the current entity was changed and the changes were committed /// /// Name of the property to check /// /// True if any of the class properties are dirty or /// True if any of the user defined PropertyType properties are dirty based on their alias, /// otherwise False /// public override bool WasPropertyDirty(string propertyName) { bool existsInEntity = base.WasPropertyDirty(propertyName); if (existsInEntity) return true; if (Properties.Contains(propertyName)) { return Properties[propertyName].WasDirty(); } return false; } #endregion } }