00001 #region License
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #endregion
00020
00021 using System;
00022 using System.Collections.Generic;
00023 using System.Diagnostics;
00024 using System.Reflection;
00025 using System.Security.Principal;
00026 using System.Text;
00027 using System.Web;
00028 using N2.Collections;
00029 using N2.Details;
00030 using N2.Integrity;
00031 using N2.Persistence;
00032 using N2.Web;
00033 using N2.Edit.Workflow;
00034 using N2.Definitions;
00035 using N2.Persistence.Proxying;
00036
00037 namespace N2
00038 {
00060 [Serializable, DebuggerDisplay("{GetType().Name}: {Name}#{ID}")]
00061 [DynamicTemplate]
00062 [RestrictParents(typeof(ContentItem))]
00063 [SortChildren(SortBy.CurrentOrder)]
00064 public abstract class ContentItem : IComparable,
00065 IComparable<ContentItem>,
00066 ICloneable,
00067 IDependentEntity<IUrlParser>,
00068 INode,
00069 IUpdatable<ContentItem>,
00070 IInterceptableType
00071 {
00072 #region Private Fields
00073 private int id;
00074 private string title;
00075 private string name;
00076 private string zoneName;
00077 private ContentItem parent = null;
00078 private DateTime created;
00079 private DateTime updated;
00080 private DateTime? published = DateTime.Now;
00081 private DateTime? expires = null;
00082 private int sortOrder;
00083 private string url = null;
00084 private bool visible = true;
00085 private ContentItem versionOf = null;
00086 private string savedBy;
00087 private IList<Security.AuthorizedRole> authorizedRoles = null;
00088 private IList<ContentItem> children = new List<ContentItem>();
00089 private IDictionary<string, Details.ContentDetail> details = new Dictionary<string, Details.ContentDetail>();
00090 private IDictionary<string, Details.DetailCollection> detailCollections = new Dictionary<string, Details.DetailCollection>();
00091 private Web.IUrlParser urlParser;
00092 private string ancestralTrail;
00093 private int versionIndex;
00094 private ContentState state = ContentState.None;
00095 private N2.Security.Permission alteredPermissions = N2.Security.Permission.None;
00096 #endregion
00097
00098 #region Constructor
00099
00100 public ContentItem()
00101 {
00102 created = DateTime.Now;
00103 updated = DateTime.Now;
00104 published = DateTime.Now;
00105 }
00106 #endregion
00107
00108 #region Persisted Properties
00109
00110 public virtual int ID
00111 {
00112 get { return id; }
00113 set { id = value; }
00114 }
00115
00117 public virtual ContentItem Parent
00118 {
00119 get { return parent; }
00120 set { parent = value; }
00121 }
00122
00124 [Details.Displayable(typeof(Web.UI.WebControls.Hn), "Text")]
00125 public virtual string Title
00126 {
00127 get { return title; }
00128 set { title = value; }
00129 }
00130
00131 private static char[] invalidCharacters = new char[] { '%', '?', '&', '/', ':' };
00133 public virtual string Name
00134 {
00135 get
00136 {
00137 return name ?? (ID > 0 ? ID.ToString() : null);
00138 }
00139 set
00140 {
00141
00142 if (string.IsNullOrEmpty(value))
00143 name = null;
00144 else
00145 name = value;
00146 url = null;
00147 }
00148 }
00149
00151 [DisplayableLiteral]
00152 public virtual string ZoneName
00153 {
00154 get { return zoneName; }
00155 set { zoneName = value; }
00156 }
00157
00159 [DisplayableLiteral]
00160 public virtual DateTime Created
00161 {
00162 get { return created; }
00163 set { created = value; }
00164 }
00165
00167 [DisplayableLiteral]
00168 public virtual DateTime Updated
00169 {
00170 get { return updated; }
00171 set { updated = value; }
00172 }
00173
00175 [DisplayableLiteral]
00176 public virtual DateTime? Published
00177 {
00178 get { return published; }
00179 set { published = value; }
00180 }
00181
00183 [DisplayableLiteral]
00184 public virtual DateTime? Expires
00185 {
00186 get { return expires; }
00187 set { expires = value != DateTime.MinValue ? value : null; }
00188 }
00189
00191 [DisplayableLiteral]
00192 public virtual int SortOrder
00193 {
00194 get { return sortOrder; }
00195 set { sortOrder = value; }
00196 }
00197
00199 [DisplayableLiteral]
00200 public virtual bool Visible
00201 {
00202 get { return visible; }
00203 set { visible = value; }
00204 }
00205
00207 public virtual ContentItem VersionOf
00208 {
00209 get { return versionOf; }
00210 set { versionOf = value; }
00211 }
00212
00214 [DisplayableLiteral]
00215 public virtual string SavedBy
00216 {
00217 get { return savedBy; }
00218 set { savedBy = value; }
00219 }
00220
00222 public virtual IDictionary<string, Details.ContentDetail> Details
00223 {
00224 get { return details; }
00225 set { details = value; }
00226 }
00227
00229 public virtual IDictionary<string, Details.DetailCollection> DetailCollections
00230 {
00231 get { return detailCollections; }
00232 set { detailCollections = value; }
00233 }
00234
00236 public virtual IList<ContentItem> Children
00237 {
00238 get { return children; }
00239 set { children = value; }
00240 }
00241
00243 public virtual string AncestralTrail
00244 {
00245 get { return ancestralTrail; }
00246 set { ancestralTrail = value; }
00247 }
00248
00250 [DisplayableLiteral]
00251 public virtual int VersionIndex
00252 {
00253 get { return versionIndex; }
00254 set { versionIndex = value; }
00255 }
00256
00257 [DisplayableLiteral]
00258 public virtual ContentState State
00259 {
00260 get { return state; }
00261 set { state = value; }
00262 }
00263
00264 [DisplayableLiteral]
00265 public virtual N2.Security.Permission AlteredPermissions
00266 {
00267 get { return alteredPermissions; }
00268 set { alteredPermissions = value; }
00269 }
00270 #endregion
00271
00272 #region Generated Properties
00273
00274 public virtual string Extension
00275 {
00276 get { return Web.Url.DefaultExtension; }
00277 }
00278
00280 public virtual bool IsPage
00281 {
00282 get { return Definitions.Static.DescriptionDictionary.GetDescription(GetContentType()).IsPage; }
00283 }
00284
00286 public virtual string Url
00287 {
00288 get
00289 {
00290 if(url == null)
00291 {
00292 if (urlParser != null)
00293 url = urlParser.BuildUrl(this);
00294 else
00295 url = FindPath(PathData.DefaultAction).RewrittenUrl;
00296 }
00297 return url;
00298 }
00299 }
00300
00302 public virtual string TemplateUrl
00303 {
00304 get { return "~/Default.aspx"; }
00305 }
00306
00308 public virtual string IconUrl
00309 {
00310 get { return N2.Web.Url.ResolveTokens(Definitions.Static.DescriptionDictionary.GetDescription(GetContentType()).IconUrl); }
00311 }
00312
00314 [Obsolete("Use the new template API: item.FindPath(PathData.DefaultAction).RewrittenUrl")]
00315 public virtual string RewrittenUrl
00316 {
00317 get { return FindPath(PathData.DefaultAction).RewrittenUrl; }
00318 }
00319
00320 #endregion
00321
00322 #region Security
00323
00324 public virtual IList<Security.AuthorizedRole> AuthorizedRoles
00325 {
00326 get
00327 {
00328 if (authorizedRoles == null)
00329 authorizedRoles = new List<Security.AuthorizedRole>();
00330 return authorizedRoles;
00331 }
00332 set { authorizedRoles = value; }
00333 }
00334
00335 #endregion
00336
00337 #region this[]
00338
00342 public virtual object this[string detailName]
00343 {
00344 get
00345 {
00346 if (detailName == null)
00347 throw new ArgumentNullException("detailName");
00348
00349 switch (detailName)
00350 {
00351 case "ID":
00352 return ID;
00353 case "Title":
00354 return Title;
00355 case "Name":
00356 return Name;
00357 case "Url":
00358 return Url;
00359 case "TemplateUrl":
00360 return TemplateUrl;
00361 default:
00362 return Utility.Evaluate(this, detailName)
00363 ?? GetDetail(detailName)
00364 ?? GetDetailCollection(detailName, false);
00365 }
00366 }
00367 set
00368 {
00369 if (string.IsNullOrEmpty(detailName))
00370 throw new ArgumentNullException("Parameter 'detailName' cannot be null or empty.", "detailName");
00371
00372 PropertyInfo info = GetContentType().GetProperty(detailName);
00373 if (info != null && info.CanWrite)
00374 {
00375 if (value != null && info.PropertyType != value.GetType())
00376 value = Utility.Convert(value, info.PropertyType);
00377 info.SetValue(this, value, null);
00378 }
00379 else if (value is Details.DetailCollection)
00380 throw new N2Exception("Cannot set a detail collection this way, add it to the DetailCollections collection instead.");
00381 else
00382 {
00383 SetDetail(detailName, value);
00384 }
00385 }
00386 }
00387 #endregion
00388
00389 #region GetDetail & SetDetail<T> Methods
00390
00391
00392
00393 public virtual object GetDetail(string detailName)
00394 {
00395 return Details.ContainsKey(detailName)
00396 ? Details[detailName].Value
00397 : null;
00398 }
00399
00404 public virtual T GetDetail<T>(string detailName, T defaultValue)
00405 {
00406 return Details.ContainsKey(detailName)
00407 ? (T)Details[detailName].Value
00408 : defaultValue;
00409 }
00410
00415 protected internal virtual void SetDetail<T>(string detailName, T value, T defaultValue)
00416 {
00417 if (value == null || !value.Equals(defaultValue))
00418 {
00419 SetDetail<T>(detailName, value);
00420 }
00421 else if (Details.ContainsKey(detailName))
00422 {
00423 details.Remove(detailName);
00424 }
00425 }
00426
00431 protected internal virtual void SetDetail<T>(string detailName, T value)
00432 {
00433 SetDetail(detailName, value, typeof(T));
00434 }
00435
00440 public virtual void SetDetail(string detailName, object value, Type valueType)
00441 {
00442 ContentDetail detail = null;
00443 if (Details.TryGetValue(detailName, out detail))
00444 {
00445 if (value != null && detail.ValueType.IsAssignableFrom(valueType))
00446 {
00447
00448 detail.Value = value;
00449 return;
00450 }
00451 }
00452
00453 if (detail != null)
00454
00455 Details.Remove(detailName);
00456 if (value != null)
00457
00458 Details.Add(detailName, N2.Details.ContentDetail.New(this, detailName, value));
00459 }
00460 #endregion
00461
00462 #region GetDetailCollection
00463
00464
00465
00466
00467 public virtual Details.DetailCollection GetDetailCollection(string collectionName, bool createWhenEmpty)
00468 {
00469 if (DetailCollections.ContainsKey(collectionName))
00470 return DetailCollections[collectionName];
00471 else if (createWhenEmpty)
00472 {
00473 Details.DetailCollection collection = new Details.DetailCollection(this, collectionName);
00474 DetailCollections.Add(collectionName, collection);
00475 return collection;
00476 }
00477 else
00478 return null;
00479 }
00480 #endregion
00481
00482 #region AddTo & GetChild & GetChildren
00483
00484 private const int SortOrderThreshold = 9999;
00485
00488 public virtual void AddTo(ContentItem newParent)
00489 {
00490 if (Parent != null && Parent != newParent && Parent.Children.Contains(this))
00491 Parent.Children.Remove(this);
00492
00493 url = null;
00494 Parent = newParent;
00495
00496 if (newParent != null && !newParent.Children.Contains(this))
00497 {
00498 IList<ContentItem> siblings = newParent.Children;
00499 if (siblings.Count > 0)
00500 {
00501 int lastOrder = siblings[siblings.Count - 1].SortOrder;
00502
00503 for (int i = siblings.Count - 2; i >= 0; i--)
00504 {
00505 if (siblings[i].SortOrder < lastOrder - SortOrderThreshold)
00506 {
00507 siblings.Insert(i + 1, this);
00508 return;
00509 }
00510 lastOrder = siblings[i].SortOrder;
00511 }
00512
00513 if (lastOrder > SortOrderThreshold)
00514 {
00515 siblings.Insert(0, this);
00516 return;
00517 }
00518 }
00519
00520 siblings.Add(this);
00521 }
00522 }
00523
00527 public virtual PathData FindPath(string remainingUrl)
00528 {
00529 if (remainingUrl == null)
00530 return PathDictionary.GetPath(this, string.Empty);
00531
00532 remainingUrl = remainingUrl.TrimStart('/');
00533
00534 if (remainingUrl.Length == 0)
00535 return PathDictionary.GetPath(this, string.Empty);
00536
00537 int slashIndex = remainingUrl.IndexOf('/');
00538 string nameSegment = HttpUtility.UrlDecode(slashIndex < 0 ? remainingUrl : remainingUrl.Substring(0, slashIndex));
00539 foreach (ContentItem child in GetChildren(new NullFilter()))
00540 {
00541 if (child.IsNamed(nameSegment))
00542 {
00543 remainingUrl = slashIndex < 0 ? null : remainingUrl.Substring(slashIndex + 1);
00544 return child.FindPath(remainingUrl);
00545 }
00546 }
00547
00548 return PathDictionary.GetPath(this, remainingUrl);
00549 }
00550
00555 public virtual ContentItem GetChild(string childName)
00556 {
00557 if (string.IsNullOrEmpty(childName))
00558 return null;
00559
00560 int slashIndex = childName.IndexOf('/');
00561 if (slashIndex == 0)
00562 {
00563 if (childName.Length == 1)
00564 return this;
00565 else
00566 return GetChild(childName.Substring(1));
00567 }
00568 if (slashIndex > 0)
00569 {
00570 string nameSegment = HttpUtility.UrlDecode(childName.Substring(0, slashIndex));
00571 foreach (ContentItem child in GetChildren(new NullFilter()))
00572 {
00573 if (child.IsNamed(nameSegment))
00574 {
00575 return child.GetChild(childName.Substring(slashIndex));
00576 }
00577 }
00578 return null;
00579 }
00580
00581
00582 foreach (ContentItem child in GetChildren(new NullFilter()))
00583 {
00584 if (child.IsNamed(childName))
00585 {
00586 return child;
00587 }
00588 }
00589 return null;
00590 }
00591
00597 protected virtual bool IsNamed(string name)
00598 {
00599 if (Name == null)
00600 return false;
00601 return Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)
00602 || (Name + Extension).Equals(name, StringComparison.InvariantCultureIgnoreCase);
00603 }
00604
00608 public virtual ItemList GetChildren()
00609 {
00610 return GetChildren(new AccessFilter());
00611 }
00612
00617 public virtual ItemList GetChildren(string childZoneName)
00618 {
00619 return GetChildren(
00620 new CompositeFilter(
00621 new ZoneFilter(childZoneName),
00622 new AccessFilter()));
00623 }
00624
00628 public virtual ItemList GetChildren(params ItemFilter[] filters)
00629 {
00630 return GetChildren(new CompositeFilter(filters));
00631 }
00632
00636 public virtual ItemList GetChildren(ItemFilter filter)
00637 {
00638 IEnumerable<ContentItem> items = VersionOf == null ? Children : VersionOf.Children;
00639 return new ItemList(items, filter);
00640 }
00641
00642 #endregion
00643
00644 #region IComparable & IComparable<ContentItem> Members
00645
00646 int IComparable.CompareTo(object obj)
00647 {
00648 if (obj is ContentItem)
00649 return SortOrder - ((ContentItem)obj).SortOrder;
00650 else
00651 return 0;
00652 }
00653 int IComparable<ContentItem>.CompareTo(ContentItem other)
00654 {
00655 return SortOrder - other.SortOrder;
00656 }
00657
00658 #endregion
00659
00660 #region ICloneable Members
00661
00662 object ICloneable.Clone()
00663 {
00664 return Clone(true);
00665 }
00666
00670 public virtual ContentItem Clone(bool includeChildren)
00671 {
00672 ContentItem cloned = (ContentItem)MemberwiseClone();
00673
00674 ClearUnclonable(cloned);
00675 CloneDetails(this, cloned);
00676 CloneChildren(this, cloned, includeChildren);
00677 CloneAuthorizedRoles(this, cloned);
00678
00679 return cloned;
00680 }
00681
00682 #region Clone Helper Methods
00683 static void CloneFields(ContentItem source, ContentItem destination)
00684 {
00685 destination.title = source.title;
00686 destination.name = source.name;
00687 destination.created = source.created;
00688 destination.updated = source.updated;
00689 destination.versionIndex = source.versionIndex;
00690 destination.visible = source.visible;
00691 destination.savedBy = source.savedBy;
00692 destination.urlParser = source.urlParser;
00693 destination.url = null;
00694 }
00695
00696 private static void ClearUnclonable(ContentItem destination)
00697 {
00698 destination.id = 0;
00699 destination.url = null;
00700 destination.parent = null;
00701 destination.versionOf = null;
00702 destination.versionIndex = 0;
00703 destination.ancestralTrail = null;
00704 destination.hashCode = null;
00705 destination.authorizedRoles = new List<Security.AuthorizedRole>();
00706 destination.children = new List<ContentItem>();
00707 destination.details = new Dictionary<string, Details.ContentDetail>();
00708 destination.detailCollections = new Dictionary<string, Details.DetailCollection>();
00709 }
00710
00711 static void CloneAuthorizedRoles(ContentItem source, ContentItem destination)
00712 {
00713 if (source.AuthorizedRoles != null)
00714 {
00715 destination.authorizedRoles = new List<Security.AuthorizedRole>();
00716 foreach (Security.AuthorizedRole role in source.AuthorizedRoles)
00717 {
00718 Security.AuthorizedRole clonedRole = role.Clone();
00719 clonedRole.EnclosingItem = destination;
00720 destination.authorizedRoles.Add(clonedRole);
00721 }
00722 }
00723 }
00724
00725 static void CloneChildren(ContentItem source, ContentItem destination, bool includeChildren)
00726 {
00727 if (includeChildren)
00728 {
00729 foreach (ContentItem child in source.Children)
00730 {
00731 ContentItem clonedChild = child.Clone(true);
00732 clonedChild.AddTo(destination);
00733 }
00734 }
00735 }
00736
00737 static void CloneDetails(ContentItem source, ContentItem destination)
00738 {
00739 foreach (Details.ContentDetail detail in source.Details.Values)
00740 {
00741 if(destination.details.ContainsKey(detail.Name))
00742 {
00743 destination.details[detail.Name].Value = detail.Value;
00744 }
00745 else
00746 {
00747 ContentDetail clonedDetail = detail.Clone();
00748 clonedDetail.EnclosingItem = destination;
00749 destination.details[detail.Name] = clonedDetail;
00750 }
00751 }
00752
00753 foreach (Details.DetailCollection collection in source.DetailCollections.Values)
00754 {
00755 Details.DetailCollection clonedCollection = collection.Clone();
00756 clonedCollection.EnclosingItem = destination;
00757 destination.DetailCollections[collection.Name] = clonedCollection;
00758 }
00759 }
00760 #endregion
00761
00762
00763 #endregion
00764
00765 #region INode Members
00766
00768 public virtual string Path
00769 {
00770 get
00771 {
00772 if (VersionOf != null)
00773 return VersionOf.Path;
00774
00775 string path = "/";
00776 for (ContentItem item = this; item.Parent != null; item = item.Parent)
00777 {
00778 if (item.Name != null)
00779 path = "/" + Uri.EscapeDataString(item.Name) + path;
00780 else
00781 path = "/" + item.ID + path;
00782 }
00783 return path;
00784 }
00785 }
00786
00787 string INode.PreviewUrl
00788 {
00789 get { return Url; }
00790 }
00791
00792 string INode.ClassNames
00793 {
00794 get
00795 {
00796 StringBuilder className = new StringBuilder();
00797
00798 if (!Published.HasValue || Published > DateTime.Now)
00799 className.Append("unpublished ");
00800 else if (Published > DateTime.Now.AddDays(-1))
00801 className.Append("day ");
00802 else if (Published > DateTime.Now.AddDays(-7))
00803 className.Append("week ");
00804 else if (Published > DateTime.Now.AddMonths(-1))
00805 className.Append("month ");
00806
00807 if (Expires.HasValue && Expires <= DateTime.Now)
00808 className.Append("expired ");
00809
00810 if (!Visible)
00811 className.Append("invisible ");
00812
00813 if (AuthorizedRoles != null && AuthorizedRoles.Count > 0)
00814 className.Append("locked ");
00815
00816 return className.ToString();
00817 }
00818 }
00819
00823 public virtual bool IsAuthorized(IPrincipal user)
00824 {
00825 if ((AlteredPermissions & N2.Security.Permission.Read) == N2.Security.Permission.None)
00826 return true;
00827
00828 if (AuthorizedRoles == null || AuthorizedRoles.Count == 0)
00829 return true;
00830
00831
00832 foreach (Security.Authorization auth in AuthorizedRoles)
00833 {
00834 if (auth.IsAuthorized(user))
00835 return true;
00836 }
00837 return false;
00838
00839 }
00840
00841 #region ILink Members
00842
00843 string Web.ILink.Contents
00844 {
00845 get { return Title; }
00846 }
00847
00848 string Web.ILink.ToolTip
00849 {
00850 get { return string.Empty; }
00851 }
00852
00853 string Web.ILink.Target
00854 {
00855 get { return string.Empty; }
00856 }
00857
00858 #endregion
00859 #endregion
00860
00861 #region Equals, HashCode and ToString Overrides
00862
00863
00864 public override bool Equals(object obj)
00865 {
00866 if (this == obj) return true;
00867 ContentItem other = obj as ContentItem;
00868 return other != null && id != 0 && id == other.id;
00869 }
00870
00871 int? hashCode;
00874 [DebuggerStepThrough]
00875 public override int GetHashCode()
00876 {
00877 if (!hashCode.HasValue)
00878 hashCode = (id > 0 ? id.GetHashCode() : base.GetHashCode());
00879 return hashCode.Value;
00880 }
00881
00884 [DebuggerStepThrough]
00885 public override string ToString()
00886 {
00887 return Name + "#" + ID;
00888 }
00889
00894 public static bool operator ==(ContentItem a, ContentItem b)
00895 {
00896 if (System.Object.ReferenceEquals(a, b))
00897 return true;
00898
00899
00900 if (((object)a == null) || ((object)b == null))
00901 {
00902 return false;
00903 }
00904
00905
00906 return a.Equals(b);
00907 }
00908
00913 public static bool operator !=(ContentItem a, ContentItem b)
00914 {
00915 return !(a == b);
00916 }
00917
00918 #endregion
00919
00920 #region IUpdatable<ContentItem> Members
00921
00922 void IUpdatable<ContentItem>.UpdateFrom(ContentItem source)
00923 {
00924 CloneFields(source, this);
00925 CloneDetails(source, this);
00926 ClearMissingDetails(source, this);
00927 }
00928
00929 private void ClearMissingDetails(ContentItem source, ContentItem destination)
00930 {
00931
00932 List<string> detailKeys = new List<string>(destination.Details.Keys);
00933 foreach(string key in detailKeys)
00934 {
00935 if (!source.Details.ContainsKey(key))
00936 destination.Details.Remove(key);
00937 }
00938
00939 List<string> collectionKeys = new List<string>(destination.DetailCollections.Keys);
00940 foreach (string key in collectionKeys)
00941 {
00942 if (source.DetailCollections.ContainsKey(key))
00943 {
00944
00945 DetailCollection destinationCollection = destination.DetailCollections[key];
00946 DetailCollection sourceCollection = source.DetailCollections[key];
00947 List<object> values = new List<object>(destinationCollection.Enumerate<object>());
00948 foreach(object value in values)
00949 {
00950 if(!sourceCollection.Contains(value))
00951 destinationCollection.Remove(value);
00952 }
00953 }
00954 else
00955
00956 destination.DetailCollections.Remove(key);
00957 }
00958 }
00959
00960 #endregion
00961
00962
00963 #region IInterceptable Members
00964
00965 public virtual Type GetContentType()
00966 {
00967 return base.GetType();
00968 }
00969
00970 #endregion
00971
00972 #region IDependentEntity<IUrlParser> Members
00973
00974 void IDependentEntity<IUrlParser>.Set(IUrlParser dependency)
00975 {
00976 urlParser = dependency;
00977 }
00978
00979 #endregion
00980 }
00981 }