using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { /// /// Defines a Content object /// [Serializable] [DataContract(IsReference = true)] public class Content : Entity, IContent { private IContentType _contentType; private int _parentId; private string _name; private int _sortOrder; private int _level; private string _path; private string _template; private int _userId; private bool _trashed; private bool _published; private string _language; private int _contentTypeId; private PropertyCollection _properties; private DateTime? _releaseDate; private DateTime? _expireDate; /// /// Constructor for creating a Content object /// /// Id of the Parent content /// ContentType for the current Content object public Content(int parentId, IContentType contentType) : this(parentId, contentType, new PropertyCollection()) { } /// /// Constructor for creating a Content object /// /// Id of the Parent content /// ContentType for the current Content object /// Collection of properties public Content(int parentId, IContentType contentType, PropertyCollection properties) { _parentId = parentId; _contentTypeId = int.Parse(contentType.Id.ToString(CultureInfo.InvariantCulture)); _contentType = contentType; _properties = properties; _properties.EnsurePropertyTypes(PropertyTypes); Version = Guid.NewGuid(); } 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 TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); private static readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.UserId); private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); private static readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); private static readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); private static readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); private static readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); private readonly static PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) { OnPropertyChanged(PropertyCollectionSelector); } /// /// Gets or sets the Id of the Parent entity /// /// Might not be necessary if handled as a relation? [DataMember] public int ParentId { get { return _parentId; } private set { _parentId = value; OnPropertyChanged(ParentIdSelector); } } /// /// Gets or sets the name of the current entity /// [DataMember] public string Name { get { return _name; } set { _name = value; OnPropertyChanged(NameSelector); } } [IgnoreDataMember] public string UrlName { get { return Name.ToLower().Replace(" ", "-"); } } //TODO Needs to implement proper url casing/syntax /// /// Gets or sets the sort order of the content entity /// [DataMember] public int SortOrder { get { return _sortOrder; } set { _sortOrder = value; OnPropertyChanged(SortOrderSelector); } } /// /// Gets or sets the level of the content entity /// [DataMember] public int Level { get { return _level; } set { _level = value; OnPropertyChanged(LevelSelector); } } /// /// Gets or sets the path /// [DataMember] public string Path //Setting this value should be handled by the class not the user { get { return _path; } set { _path = value; OnPropertyChanged(PathSelector); } } /// /// Path to the template used by this Content /// This is used to override the default one from the ContentType /// /// If no template is explicitly set on the Content object, the Default template from the ContentType will be returned [DataMember] public virtual string Template { get { if (string.IsNullOrEmpty(_template) || _template == null) return _contentType.DefaultTemplate; return _template; } set { _template = value; OnPropertyChanged(TemplateSelector); } } /// /// Id of the user who created this Content /// [DataMember] public int UserId { get { return _userId; } set { _userId = value; OnPropertyChanged(UserIdSelector); } } /// /// Gets the current status of the Content /// public ContentStatus Status { get { if(Trashed) return ContentStatus.Trashed; if(ExpireDate.HasValue && DateTime.UtcNow > ExpireDate.Value) return ContentStatus.Expired; if(ReleaseDate.HasValue && ReleaseDate.Value > DateTime.UtcNow) return ContentStatus.AwaitingRelease; if(Published) return ContentStatus.Published; return ContentStatus.Unpublished; } } /// /// 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 bool Trashed //Setting this value should be handled by the class not the user { get { return _trashed; } internal set { _trashed = value; OnPropertyChanged(TrashedSelector); } } /// /// Boolean indicating whether this Content is Published or not /// /// Setting Published to true/false should be private or internal [DataMember] public bool Published { get { return _published; } internal set { _published = value; OnPropertyChanged(PublishedSelector); } } /// /// Language of the data contained within this Content object /// [DataMember] internal string Language { get { return _language; } set { _language = value; OnPropertyChanged(LanguageSelector); } } /// /// Guid Id of the curent Version /// [DataMember] public Guid Version { get; internal set; } /// /// Integer Id of the default ContentType /// [DataMember] public int ContentTypeId { get { return _contentTypeId; } protected set { _contentTypeId = value; OnPropertyChanged(DefaultContentTypeIdSelector); } } /// /// Collection of properties, which make up all the data available for this Content object /// /// Properties are loaded as part of the Content object graph [DataMember] public PropertyCollection Properties { get { return _properties; } set { _properties = value; _properties.CollectionChanged += PropertiesChanged; } } /// /// Set property values by alias with an annonymous object /// [IgnoreDataMember] public object PropertyValues { set { if (value == null) throw new Exception("No properties has been passed in"); var propertyInfos = value.GetType().GetProperties(); foreach (var propertyInfo in propertyInfos) { //Check if a PropertyType with alias exists thus being a valid property var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyInfo.Name); if (propertyType == null) throw new Exception( string.Format( "The property alias {0} is not valid, because no PropertyType with this alias exists", propertyInfo.Name)); //Check if a Property with the alias already exists in the collection thus being updated or inserted var item = Properties.FirstOrDefault(x => x.Alias == propertyInfo.Name); if (item != null) { item.Value = propertyInfo.GetValue(value, null); //Update item with newly added value Properties.Add(item); } else { //Create new Property to add to collection var property = propertyType.CreatePropertyFromValue(propertyInfo.GetValue(value, null)); Properties.Add(property); } } } } /// /// List of PropertyGroups available on this Content object /// [IgnoreDataMember] public IEnumerable PropertyGroups { get { return _contentType.CompositionPropertyGroups; } } /// /// List of PropertyTypes available on this Content object /// [IgnoreDataMember] public IEnumerable PropertyTypes { get { return _contentType.CompositionPropertyTypes; } } /// /// The date this Content should be released and thus be published /// [DataMember] public DateTime? ReleaseDate { get { return _releaseDate; } set { if(value.HasValue && value.Value > DateTime.UtcNow && Published) ChangePublishedState(false); if (value.HasValue && value.Value < DateTime.UtcNow && !Published) ChangePublishedState(true); _releaseDate = value; OnPropertyChanged(ReleaseDateSelector); } } /// /// The date this Content should expire and thus be unpublished /// [DataMember] public DateTime? ExpireDate { get { return _expireDate; } set { if(value.HasValue && DateTime.UtcNow > value.Value && Published) ChangePublishedState(false); _expireDate = value; OnPropertyChanged(ExpireDateSelector); } } /// /// Gets the ContentType used by this content object /// [IgnoreDataMember] public IContentType ContentType { get { return _contentType; } } /// /// Changes the for the current content object /// /// New ContentType for this content /// Leaves PropertyTypes intact after change public void ChangeContentType(IContentType contentType) { ContentTypeId = contentType.Id; _contentType = contentType; _properties.EnsurePropertyTypes(PropertyTypes); _properties.CollectionChanged += PropertiesChanged; } /// /// Changes the for the current content object and removes PropertyTypes, /// which are not part of the new ContentType. /// /// New ContentType for this content /// Boolean indicating whether to clear PropertyTypes upon change public void ChangeContentType(IContentType contentType, bool clearProperties) { if(clearProperties) { ContentTypeId = contentType.Id; _contentType = contentType; _properties.EnsureCleanPropertyTypes(PropertyTypes); _properties.CollectionChanged += PropertiesChanged; return; } ChangeContentType(contentType); } /// /// 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 bool HasProperty(string propertyTypeAlias) { return Properties.Contains(propertyTypeAlias); } /// /// Gets the value of a Property /// /// Alias of the PropertyType /// Value as an public 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 TPassType GetValue(string propertyTypeAlias) { return (TPassType)Properties[propertyTypeAlias].Value; } /// /// Sets the value of a Property /// /// Alias of the PropertyType /// Value to set for the Property public void SetValue(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)); } /// /// Method to call when Entity is being saved /// /// Created date is set and a Unique key is assigned internal override void AddingEntity() { base.AddingEntity(); Key = Guid.NewGuid(); } /// /// Method to call when Entity is being updated /// /// Modified Date is set and a new Version guid is set internal override void UpdatingEntity() { base.UpdatingEntity(); Version = Guid.NewGuid(); } /// /// Changes the Published state of the content object /// /// Boolean indicating whether content is published (true) or unpublished (false) internal void ChangePublishedState(bool isPublished) { Published = isPublished; //NOTE Should this be checked against the Expire/Release dates? //TODO possibly create new (unpublished version)? } /// /// Changes the Trashed state of the content object /// /// Boolean indicating whether content is trashed (true) or not trashed (false) /// internal void ChangeTrashedState(bool isTrashed, int parentId = -1) { Trashed = isTrashed; //If Content is trashed the parent id should be set to that of the RecycleBin if(isTrashed) { ParentId = -20; } else//otherwise set the parent id to the optional parameter, -1 being the fallback { ParentId = parentId; } //If the content is trashed and is published it should be marked as unpublished if (isTrashed && Published) { ChangePublishedState(false); } } } }