From fb02dca73b88e9633dfc7e42fd756ad3e94d976e Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Wed, 1 Feb 2023 16:32:45 -0600 Subject: [PATCH] Resolve warnings in DotNetNuke.Syndication --- .../Services/Syndication/RssHandler.cs | 3 +- .../Syndication/SyndicationHandlerBase.cs | 113 +-- .../Syndication/DotNetNuke.Syndication.csproj | 2 + DNN Platform/Syndication/OPML/Opml.cs | 923 ++++++++---------- .../Syndication/OPML/OpmlDownloadManager.cs | 468 ++++----- DNN Platform/Syndication/OPML/OpmlOutline.cs | 125 +-- DNN Platform/Syndication/OPML/OpmlOutlines.cs | 2 +- .../Syndication/RSS/GenericRssChannel.cs | 144 ++- .../Syndication/RSS/GenericRssElement.cs | 44 +- .../Syndication/RSS/RssChannelBase.cs | 39 +- DNN Platform/Syndication/RSS/RssChannelDom.cs | 95 +- DNN Platform/Syndication/RSS/RssDataSource.cs | 5 +- .../Syndication/RSS/RssDataSourceView.cs | 5 +- .../Syndication/RSS/RssDownloadManager.cs | 503 +++++----- .../Syndication/RSS/RssElementBase.cs | 91 +- .../RSS/RssElementCustomTypeDescriptor.cs | 256 +++-- .../Syndication/RSS/RssHttpHandlerBase.cs | 70 +- .../Syndication/RSS/RssHttpHandlerHelper.cs | 14 +- DNN Platform/Syndication/RSS/RssHyperLink.cs | 96 +- DNN Platform/Syndication/RSS/RssXmlHelper.cs | 10 +- DNN Platform/Syndication/Settings.cs | 2 + 21 files changed, 1376 insertions(+), 1634 deletions(-) diff --git a/DNN Platform/Library/Services/Syndication/RssHandler.cs b/DNN Platform/Library/Services/Syndication/RssHandler.cs index 5c4b382d8d8..3354f99ba59 100644 --- a/DNN Platform/Library/Services/Syndication/RssHandler.cs +++ b/DNN Platform/Library/Services/Syndication/RssHandler.cs @@ -16,6 +16,7 @@ namespace DotNetNuke.Services.Syndication using DotNetNuke.Services.Search.Entities; using DotNetNuke.Services.Search.Internals; + /// An HTTP handler for serving an RSS feed. public class RssHandler : SyndicationHandlerBase { /// @@ -99,7 +100,7 @@ protected override void OnPreRender(EventArgs ea) } /// Creates an RSS Item. - /// + /// The search result to convert to an RSS item. /// A new instance. private GenericRssElement GetRssItem(SearchResult searchResult) { diff --git a/DNN Platform/Library/Services/Syndication/SyndicationHandlerBase.cs b/DNN Platform/Library/Services/Syndication/SyndicationHandlerBase.cs index a4b118b8c14..682d5224003 100644 --- a/DNN Platform/Library/Services/Syndication/SyndicationHandlerBase.cs +++ b/DNN Platform/Library/Services/Syndication/SyndicationHandlerBase.cs @@ -1,66 +1,59 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Services.Syndication -{ - using System.Web; +namespace DotNetNuke.Services.Syndication +{ + using System.Web; - using DotNetNuke.Common; - using DotNetNuke.Common.Utilities; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; using DotNetNuke.Entities.Portals; - public class SyndicationHandlerBase : GenericRssHttpHandlerBase - { - private int moduleId = Null.NullInteger; - - private int tabId = Null.NullInteger; - - public PortalSettings Settings - { - get - { - return Globals.GetPortalSettings(); - } - } - - public int TabId - { - get - { - if (this.tabId == Null.NullInteger && this.Request.QueryString["tabid"] != null) - { - if (!int.TryParse(this.Request.QueryString["tabid"], out this.tabId)) - { - this.tabId = Null.NullInteger; - } - } - - return this.tabId; - } - } - - public int ModuleId - { - get - { - if (this.moduleId == Null.NullInteger && this.Request.QueryString["moduleid"] != null) - { - if (!int.TryParse(this.Request.QueryString["moduleid"], out this.moduleId)) - { - this.moduleId = Null.NullInteger; - } - } - - return this.moduleId; - } - } - - public HttpRequest Request - { - get - { - return HttpContext.Current.Request; - } - } - } -} + /// An HTTP handler for generating an RSS feed. + public class SyndicationHandlerBase : GenericRssHttpHandlerBase + { + private int moduleId = Null.NullInteger; + + private int tabId = Null.NullInteger; + + /// Gets the portal settings. + public PortalSettings Settings => Globals.GetPortalSettings(); + + /// Gets the tab ID of the request. + public int TabId + { + get + { + if (this.tabId == Null.NullInteger && this.Request.QueryString["tabid"] != null) + { + if (!int.TryParse(this.Request.QueryString["tabid"], out this.tabId)) + { + this.tabId = Null.NullInteger; + } + } + + return this.tabId; + } + } + + /// Gets the module ID of the request. + public int ModuleId + { + get + { + if (this.moduleId == Null.NullInteger && this.Request.QueryString["moduleid"] != null) + { + if (!int.TryParse(this.Request.QueryString["moduleid"], out this.moduleId)) + { + this.moduleId = Null.NullInteger; + } + } + + return this.moduleId; + } + } + + /// Gets the HTTP request. + public HttpRequest Request => HttpContext.Current.Request; + } +} diff --git a/DNN Platform/Syndication/DotNetNuke.Syndication.csproj b/DNN Platform/Syndication/DotNetNuke.Syndication.csproj index 9df86a549e6..953957ef143 100644 --- a/DNN Platform/Syndication/DotNetNuke.Syndication.csproj +++ b/DNN Platform/Syndication/DotNetNuke.Syndication.csproj @@ -42,6 +42,7 @@ bin\DotNetNuke.Services.Syndication.xml 1591 7 + true pdbonly @@ -55,6 +56,7 @@ bin\DotNetNuke.Services.Syndication.xml true 7 + true diff --git a/DNN Platform/Syndication/OPML/Opml.cs b/DNN Platform/Syndication/OPML/Opml.cs index ada6d4c2d6f..e35dbd1cc5d 100644 --- a/DNN Platform/Syndication/OPML/Opml.cs +++ b/DNN Platform/Syndication/OPML/Opml.cs @@ -1,535 +1,400 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Services.Syndication -{ - using System; - using System.Xml; +namespace DotNetNuke.Services.Syndication +{ + using System; + using System.Xml; using DotNetNuke.Instrumentation; - /// Class for managing an OPML feed. - public class Opml - { - private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(Opml)); - private DateTime dateCreated = DateTime.MinValue; - private DateTime dateModified = DateTime.MinValue; - private string docs = string.Empty; - private string expansionState = string.Empty; - private OpmlOutlines outlines; - private string ownerEmail = string.Empty; - private string ownerId = string.Empty; - private string ownerName = string.Empty; - private string title = string.Empty; - private DateTime utcExpiry = DateTime.Now.AddMinutes(180); - private string vertScrollState = string.Empty; - private string windowBottom = string.Empty; - private string windowLeft = string.Empty; - private string windowRight = string.Empty; - private string windowTop = string.Empty; - private XmlDocument opmlDoc; - - public Opml() - { - this.outlines = new OpmlOutlines(); - } - - public DateTime UtcExpiry - { - get - { - return this.utcExpiry; - } - - set - { - this.utcExpiry = value; - } - } - - public string Title - { - get - { - return this.title; - } - - set - { - this.title = value; - } - } - - public DateTime DateCreated - { - get - { - return this.dateCreated; - } - - set - { - this.dateCreated = value; - } - } - - public DateTime DateModified - { - get - { - return this.dateModified; - } - - set - { - this.dateModified = value; - } - } - - public string OwnerName - { - get - { - return this.ownerName; - } - - set - { - this.ownerName = value; - } - } - - public string OwnerEmail - { - get - { - return this.ownerEmail; - } - - set - { - this.ownerEmail = value; - } - } - - public string OwnerId - { - get - { - return this.ownerId; - } - - set - { - this.ownerId = value; - } - } - - public string Docs - { - get - { - return this.docs; - } - - set - { - this.docs = value; - } - } - - public string ExpansionState - { - get - { - return this.expansionState; - } - - set - { - this.expansionState = value; - } - } - - public string VertScrollState - { - get - { - return this.vertScrollState; - } - - set - { - this.vertScrollState = value; - } - } - - public string WindowTop - { - get - { - return this.windowTop; - } - - set - { - this.windowTop = value; - } - } - - public string WindowLeft - { - get - { - return this.windowLeft; - } - - set - { - this.windowLeft = value; - } - } - - public string WindowBottom - { - get - { - return this.windowBottom; - } - - set - { - this.windowBottom = value; - } - } - - public string WindowRight - { - get - { - return this.windowRight; - } - - set - { - this.windowRight = value; - } - } - - public OpmlOutlines Outlines - { - get - { - return this.outlines; - } - - set - { - this.outlines = value; - } - } - - public static Opml LoadFromUrl(Uri uri) - { - return OpmlDownloadManager.GetOpmlFeed(uri); - } - - public static Opml LoadFromFile(string path) - { - try - { - var opmlDoc = new XmlDocument { XmlResolver = null }; - opmlDoc.Load(path); - - return LoadFromXml(opmlDoc); - } - catch - { - return new Opml(); - } - } - - public static Opml LoadFromXml(XmlDocument doc) - { - var @out = new Opml(); - try - { - // Parse head - XmlNode head = doc.GetElementsByTagName("head")[0]; - XmlNode title = head.SelectSingleNode("./title"); - XmlNode dateCreated = head.SelectSingleNode("./dateCreated"); - XmlNode dateModified = head.SelectSingleNode("./dateModified"); - XmlNode ownerName = head.SelectSingleNode("./ownerName"); - XmlNode ownerEmail = head.SelectSingleNode("./ownerEmail"); - XmlNode ownerId = head.SelectSingleNode("./ownerId"); - XmlNode docs = head.SelectSingleNode("./docs"); - XmlNode expansionState = head.SelectSingleNode("./expansionState"); - XmlNode vertScrollState = head.SelectSingleNode("./vertScrollState"); - XmlNode windowTop = head.SelectSingleNode("./windowTop"); - XmlNode windowLeft = head.SelectSingleNode("./windowLeft"); - XmlNode windowBottom = head.SelectSingleNode("./windowBottom"); - XmlNode windowRight = head.SelectSingleNode("./windowRight"); - - if (title != null) - { - @out.Title = title.InnerText; - } - - if (dateCreated != null) - { - @out.DateCreated = DateTime.Parse(dateCreated.InnerText); - } - - if (dateModified != null) - { - @out.DateModified = DateTime.Parse(dateModified.InnerText); - } - - if (ownerName != null) - { - @out.OwnerName = ownerName.InnerText; - } - - if (ownerEmail != null) - { - @out.OwnerEmail = ownerEmail.InnerText; - } - - if (ownerId != null) - { - @out.OwnerId = ownerId.InnerText; - } - - if (docs != null) - { - @out.Docs = docs.InnerText; - } - - if (expansionState != null) - { - @out.ExpansionState = expansionState.InnerText; - } - - if (vertScrollState != null) - { - @out.VertScrollState = vertScrollState.InnerText; - } - - if (windowTop != null) - { - @out.WindowTop = windowTop.InnerText; - } - - if (windowLeft != null) - { - @out.WindowLeft = windowLeft.InnerText; - } - - if (windowBottom != null) - { - @out.WindowBottom = windowBottom.InnerText; - } - - if (windowLeft != null) - { - @out.WindowLeft = windowLeft.InnerText; - } - - // Parse body - XmlNode body = doc.GetElementsByTagName("body")[0]; - XmlNodeList outlineList = body.SelectNodes("./outline"); - foreach (XmlElement outline in outlineList) - { - @out.Outlines.Add(ParseXml(outline)); - } - - return @out; - } - catch - { - return new Opml(); - } - } - - public void AddOutline(OpmlOutline item) - { - this.outlines.Add(item); - } - - public void AddOutline(string text, string type, Uri xmlUrl, string category) - { - this.AddOutline(text, type, xmlUrl, category, null); - } - - public void AddOutline(string text, string type, Uri xmlUrl, string category, OpmlOutlines outlines) - { - var item = new OpmlOutline(); - item.Text = text; - item.Type = type; - item.XmlUrl = xmlUrl; - item.Category = category; - item.Outlines = outlines; - this.outlines.Add(item); - } - - public string GetXml() - { - return this.opmlDoc.OuterXml; - } - - public void Save(string fileName) - { - this.opmlDoc = new XmlDocument { XmlResolver = null }; - XmlElement opml = this.opmlDoc.CreateElement("opml"); - opml.SetAttribute("version", "2.0"); - this.opmlDoc.AppendChild(opml); - - // create head - XmlElement head = this.opmlDoc.CreateElement("head"); - opml.AppendChild(head); - - // set Title - XmlElement title = this.opmlDoc.CreateElement("title"); - title.InnerText = this.Title; - head.AppendChild(title); - - // set date created - XmlElement dateCreated = this.opmlDoc.CreateElement("dateCreated"); - dateCreated.InnerText = this.DateCreated != DateTime.MinValue ? this.DateCreated.ToString("r", null) : DateTime.Now.ToString("r", null); - head.AppendChild(dateCreated); - - // set date modified - XmlElement dateModified = this.opmlDoc.CreateElement("dateModified"); - dateCreated.InnerText = this.DateModified != DateTime.MinValue ? this.DateModified.ToString("r", null) : DateTime.Now.ToString("r", null); - head.AppendChild(dateModified); - - // set owner email - XmlElement ownerEmail = this.opmlDoc.CreateElement("ownerEmail"); - ownerEmail.InnerText = this.OwnerEmail; - head.AppendChild(ownerEmail); - - // set owner name - XmlElement ownerName = this.opmlDoc.CreateElement("ownerName"); - ownerName.InnerText = this.OwnerName; - head.AppendChild(ownerName); - - // set owner id - XmlElement ownerId = this.opmlDoc.CreateElement("ownerId"); - ownerId.InnerText = this.OwnerId; - head.AppendChild(ownerId); - - // set docs - XmlElement docs = this.opmlDoc.CreateElement("docs"); - docs.InnerText = this.Docs; - head.AppendChild(docs); - - // set expansionState - XmlElement expansionState = this.opmlDoc.CreateElement("expansionState"); - expansionState.InnerText = this.ExpansionState; - head.AppendChild(expansionState); - - // set vertScrollState - XmlElement vertScrollState = this.opmlDoc.CreateElement("vertScrollState"); - vertScrollState.InnerText = this.VertScrollState; - head.AppendChild(vertScrollState); - - // set windowTop - XmlElement windowTop = this.opmlDoc.CreateElement("windowTop"); - windowTop.InnerText = this.WindowTop; - head.AppendChild(windowTop); - - // set windowLeft - XmlElement windowLeft = this.opmlDoc.CreateElement("windowLeft"); - windowLeft.InnerText = this.WindowLeft; - head.AppendChild(windowLeft); - - // set windowBottom - XmlElement windowBottom = this.opmlDoc.CreateElement("windowBottom"); - windowBottom.InnerText = this.WindowBottom; - head.AppendChild(windowBottom); - - // set windowRight - XmlElement windowRight = this.opmlDoc.CreateElement("windowRight"); - windowRight.InnerText = this.WindowRight; - head.AppendChild(windowRight); - - // create body - XmlElement opmlBody = this.opmlDoc.CreateElement("body"); - opml.AppendChild(opmlBody); - - foreach (OpmlOutline outline in this.outlines) - { - opmlBody.AppendChild(outline.ToXml); - } - - this.opmlDoc.Save(fileName); - } - - internal static OpmlOutline ParseXml(XmlElement node) - { - var newOutline = new OpmlOutline(); - - newOutline.Text = ParseElement(node, "text"); - newOutline.Type = ParseElement(node, "type"); - newOutline.IsComment = ParseElement(node, "isComment") == "true" ? true : false; - newOutline.IsBreakpoint = ParseElement(node, "isBreakpoint") == "true" ? true : false; - try - { - newOutline.Created = DateTime.Parse(ParseElement(node, "created")); - } - catch (Exception ex) - { - Logger.Error(ex); - } - - newOutline.Category = ParseElement(node, "category"); - try - { - newOutline.XmlUrl = new Uri(ParseElement(node, "xmlUrl")); - } - catch (Exception ex) - { - Logger.Error(ex); - } - - try - { - newOutline.HtmlUrl = new Uri(ParseElement(node, "htmlUrl")); - } - catch (Exception ex) - { - Logger.Error(ex); - } - - newOutline.Language = ParseElement(node, "language"); - newOutline.Title = ParseElement(node, "title"); - - try - { - newOutline.Url = new Uri(ParseElement(node, "url")); - } - catch (Exception ex) - { - Logger.Error(ex); - } - - newOutline.Description = ParseElement(node, "description"); - - if (node.HasChildNodes) - { - foreach (XmlElement childNode in node.SelectNodes("./outline")) - { - newOutline.Outlines.Add(ParseXml(childNode)); - } - } - - return newOutline; - } - - private static string ParseElement(XmlElement node, string attribute) - { - string attrValue = node.GetAttribute(attribute); - return !string.IsNullOrEmpty(attrValue) ? attrValue : string.Empty; - } - } -} + /// Class for managing an OPML feed. + public class Opml + { + private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(Opml)); + private XmlDocument opmlDoc; + + /// Initializes a new instance of the class. + public Opml() + { + this.Outlines = new OpmlOutlines(); + } + + /// Gets or sets the expiration (in UTC). + public DateTime UtcExpiry { get; set; } = DateTime.Now.AddMinutes(180); + + /// Gets or sets the title. + public string Title { get; set; } = string.Empty; + + /// Gets or sets the created date. + public DateTime DateCreated { get; set; } = DateTime.MinValue; + + /// Gets or sets the date modified. + public DateTime DateModified { get; set; } = DateTime.MinValue; + + /// Gets or sets owner name. + public string OwnerName { get; set; } = string.Empty; + + /// Gets or sets the owner email. + public string OwnerEmail { get; set; } = string.Empty; + + /// Gets or sets the owner ID. + public string OwnerId { get; set; } = string.Empty; + + /// Gets or sets the docs. + public string Docs { get; set; } = string.Empty; + + /// Gets or sets the expansion state. + public string ExpansionState { get; set; } = string.Empty; + + /// Gets or sets the vertical scroll state. + public string VertScrollState { get; set; } = string.Empty; + + /// Gets or sets window top position. + public string WindowTop { get; set; } = string.Empty; + + /// Gets or sets window left position. + public string WindowLeft { get; set; } = string.Empty; + + /// Gets or sets the window bottom position. + public string WindowBottom { get; set; } = string.Empty; + + /// Gets or sets the window right position. + public string WindowRight { get; set; } = string.Empty; + + /// Gets or sets the outlines. + public OpmlOutlines Outlines { get; set; } + + /// Loads an OPML feed from a URL. + /// The feed's URL. + /// The OPML feed, or an empty OPML feed if there's an error loading it. + public static Opml LoadFromUrl(Uri uri) + { + return OpmlDownloadManager.GetOpmlFeed(uri); + } + + /// Loads an OPML feed from a file path. + /// The file path. + /// The OPML feed, or an empty OPML feed if there's an error loading it. + public static Opml LoadFromFile(string path) + { + try + { + var opmlDoc = new XmlDocument { XmlResolver = null }; + opmlDoc.Load(path); + + return LoadFromXml(opmlDoc); + } + catch + { + return new Opml(); + } + } + + /// Loads an OPML feed from an XML document. + /// The XML document. + /// The OPML feed, or an empty OPML feed if there's an error loading it. + public static Opml LoadFromXml(XmlDocument doc) + { + var @out = new Opml(); + try + { + // Parse head + XmlNode head = doc.GetElementsByTagName("head")[0]; + XmlNode title = head.SelectSingleNode("./title"); + XmlNode dateCreated = head.SelectSingleNode("./dateCreated"); + XmlNode dateModified = head.SelectSingleNode("./dateModified"); + XmlNode ownerName = head.SelectSingleNode("./ownerName"); + XmlNode ownerEmail = head.SelectSingleNode("./ownerEmail"); + XmlNode ownerId = head.SelectSingleNode("./ownerId"); + XmlNode docs = head.SelectSingleNode("./docs"); + XmlNode expansionState = head.SelectSingleNode("./expansionState"); + XmlNode vertScrollState = head.SelectSingleNode("./vertScrollState"); + XmlNode windowTop = head.SelectSingleNode("./windowTop"); + XmlNode windowLeft = head.SelectSingleNode("./windowLeft"); + XmlNode windowBottom = head.SelectSingleNode("./windowBottom"); + XmlNode windowRight = head.SelectSingleNode("./windowRight"); + + if (title != null) + { + @out.Title = title.InnerText; + } + + if (dateCreated != null) + { + @out.DateCreated = DateTime.Parse(dateCreated.InnerText); + } + + if (dateModified != null) + { + @out.DateModified = DateTime.Parse(dateModified.InnerText); + } + + if (ownerName != null) + { + @out.OwnerName = ownerName.InnerText; + } + + if (ownerEmail != null) + { + @out.OwnerEmail = ownerEmail.InnerText; + } + + if (ownerId != null) + { + @out.OwnerId = ownerId.InnerText; + } + + if (docs != null) + { + @out.Docs = docs.InnerText; + } + + if (expansionState != null) + { + @out.ExpansionState = expansionState.InnerText; + } + + if (vertScrollState != null) + { + @out.VertScrollState = vertScrollState.InnerText; + } + + if (windowTop != null) + { + @out.WindowTop = windowTop.InnerText; + } + + if (windowLeft != null) + { + @out.WindowLeft = windowLeft.InnerText; + } + + if (windowBottom != null) + { + @out.WindowBottom = windowBottom.InnerText; + } + + if (windowLeft != null) + { + @out.WindowLeft = windowLeft.InnerText; + } + + // Parse body + XmlNode body = doc.GetElementsByTagName("body")[0]; + XmlNodeList outlineList = body.SelectNodes("./outline"); + foreach (XmlElement outline in outlineList) + { + @out.Outlines.Add(ParseXml(outline)); + } + + return @out; + } + catch + { + return new Opml(); + } + } + + /// Adds an to the . + /// The outline item. + public void AddOutline(OpmlOutline item) + { + this.Outlines.Add(item); + } + + /// Adds an item to the . + /// The item text. + /// The item type. + /// The XML URL for the item. + /// The item's category. + public void AddOutline(string text, string type, Uri xmlUrl, string category) + { + this.AddOutline(text, type, xmlUrl, category, null); + } + + /// Adds an item to the . + /// The item text. + /// The item type. + /// The XML URL for the item. + /// The item's category. + /// The item's outlines. + public void AddOutline(string text, string type, Uri xmlUrl, string category, OpmlOutlines outlines) + { + var item = new OpmlOutline(); + item.Text = text; + item.Type = type; + item.XmlUrl = xmlUrl; + item.Category = category; + item.Outlines = outlines; + this.Outlines.Add(item); + } + + /// Get the OPML doc as XML. + /// The XML markup for this OPML feed. + public string GetXml() + { + return this.opmlDoc.OuterXml; + } + + /// Saves this OPML feed as an XML file. + /// The file path. + public void Save(string fileName) + { + this.opmlDoc = new XmlDocument { XmlResolver = null }; + XmlElement opml = this.opmlDoc.CreateElement("opml"); + opml.SetAttribute("version", "2.0"); + this.opmlDoc.AppendChild(opml); + + // create head + XmlElement head = this.opmlDoc.CreateElement("head"); + opml.AppendChild(head); + + // set Title + XmlElement title = this.opmlDoc.CreateElement("title"); + title.InnerText = this.Title; + head.AppendChild(title); + + // set date created + XmlElement dateCreated = this.opmlDoc.CreateElement("dateCreated"); + dateCreated.InnerText = this.DateCreated != DateTime.MinValue ? this.DateCreated.ToString("r", null) : DateTime.Now.ToString("r", null); + head.AppendChild(dateCreated); + + // set date modified + XmlElement dateModified = this.opmlDoc.CreateElement("dateModified"); + dateCreated.InnerText = this.DateModified != DateTime.MinValue ? this.DateModified.ToString("r", null) : DateTime.Now.ToString("r", null); + head.AppendChild(dateModified); + + // set owner email + XmlElement ownerEmail = this.opmlDoc.CreateElement("ownerEmail"); + ownerEmail.InnerText = this.OwnerEmail; + head.AppendChild(ownerEmail); + + // set owner name + XmlElement ownerName = this.opmlDoc.CreateElement("ownerName"); + ownerName.InnerText = this.OwnerName; + head.AppendChild(ownerName); + + // set owner id + XmlElement ownerId = this.opmlDoc.CreateElement("ownerId"); + ownerId.InnerText = this.OwnerId; + head.AppendChild(ownerId); + + // set docs + XmlElement docs = this.opmlDoc.CreateElement("docs"); + docs.InnerText = this.Docs; + head.AppendChild(docs); + + // set expansionState + XmlElement expansionState = this.opmlDoc.CreateElement("expansionState"); + expansionState.InnerText = this.ExpansionState; + head.AppendChild(expansionState); + + // set vertScrollState + XmlElement vertScrollState = this.opmlDoc.CreateElement("vertScrollState"); + vertScrollState.InnerText = this.VertScrollState; + head.AppendChild(vertScrollState); + + // set windowTop + XmlElement windowTop = this.opmlDoc.CreateElement("windowTop"); + windowTop.InnerText = this.WindowTop; + head.AppendChild(windowTop); + + // set windowLeft + XmlElement windowLeft = this.opmlDoc.CreateElement("windowLeft"); + windowLeft.InnerText = this.WindowLeft; + head.AppendChild(windowLeft); + + // set windowBottom + XmlElement windowBottom = this.opmlDoc.CreateElement("windowBottom"); + windowBottom.InnerText = this.WindowBottom; + head.AppendChild(windowBottom); + + // set windowRight + XmlElement windowRight = this.opmlDoc.CreateElement("windowRight"); + windowRight.InnerText = this.WindowRight; + head.AppendChild(windowRight); + + // create body + XmlElement opmlBody = this.opmlDoc.CreateElement("body"); + opml.AppendChild(opmlBody); + + foreach (OpmlOutline outline in this.Outlines) + { + opmlBody.AppendChild(outline.ToXml); + } + + this.opmlDoc.Save(fileName); + } + + /// Parses a into an . + /// The XML element to parse. + /// A new instance. + internal static OpmlOutline ParseXml(XmlElement node) + { + var newOutline = new OpmlOutline(); + + newOutline.Text = ParseElement(node, "text"); + newOutline.Type = ParseElement(node, "type"); + newOutline.IsComment = ParseElement(node, "isComment") == "true" ? true : false; + newOutline.IsBreakpoint = ParseElement(node, "isBreakpoint") == "true" ? true : false; + try + { + newOutline.Created = DateTime.Parse(ParseElement(node, "created")); + } + catch (Exception ex) + { + Logger.Error(ex); + } + + newOutline.Category = ParseElement(node, "category"); + try + { + newOutline.XmlUrl = new Uri(ParseElement(node, "xmlUrl")); + } + catch (Exception ex) + { + Logger.Error(ex); + } + + try + { + newOutline.HtmlUrl = new Uri(ParseElement(node, "htmlUrl")); + } + catch (Exception ex) + { + Logger.Error(ex); + } + + newOutline.Language = ParseElement(node, "language"); + newOutline.Title = ParseElement(node, "title"); + + try + { + newOutline.Url = new Uri(ParseElement(node, "url")); + } + catch (Exception ex) + { + Logger.Error(ex); + } + + newOutline.Description = ParseElement(node, "description"); + + if (node.HasChildNodes) + { + foreach (XmlElement childNode in node.SelectNodes("./outline")) + { + newOutline.Outlines.Add(ParseXml(childNode)); + } + } + + return newOutline; + } + + private static string ParseElement(XmlElement node, string attribute) + { + string attrValue = node.GetAttribute(attribute); + return !string.IsNullOrEmpty(attrValue) ? attrValue : string.Empty; + } + } +} diff --git a/DNN Platform/Syndication/OPML/OpmlDownloadManager.cs b/DNN Platform/Syndication/OPML/OpmlDownloadManager.cs index 54cd9486576..b5c0f8d926e 100644 --- a/DNN Platform/Syndication/OPML/OpmlDownloadManager.cs +++ b/DNN Platform/Syndication/OPML/OpmlDownloadManager.cs @@ -1,237 +1,243 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Services.Syndication -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Net; - using System.Web; - using System.Xml; +namespace DotNetNuke.Services.Syndication +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Web; + using System.Xml; using DotNetNuke.Instrumentation; - /// Helper class that provides memory and disk caching of the downloaded feeds. - internal class OpmlDownloadManager - { - private const string OPMLDir = "/OPML/"; - private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(OpmlDownloadManager)); - private static readonly OpmlDownloadManager TheManager = new OpmlDownloadManager(); - - private readonly Dictionary cache; - private readonly int defaultTtlMinutes; - private readonly string directoryOnDisk; - - private OpmlDownloadManager() - { - // create in-memory cache - this.cache = new Dictionary(); - - this.defaultTtlMinutes = 60; - - // prepare disk directory - this.directoryOnDisk = PrepareTempDir(); - } - - public static Opml GetOpmlFeed(Uri uri) - { - return TheManager.GetFeed(uri); - } - - internal Opml GetFeed(Uri uri) - { - Opml opmlFeed = null; - - lock (this.cache) - { - if (this.cache.TryGetValue(uri.AbsoluteUri, out opmlFeed)) - { - if (DateTime.UtcNow > opmlFeed.UtcExpiry) - { - this.cache.Remove(uri.AbsoluteUri); - opmlFeed = null; - } - } - } - - if (opmlFeed == null) - { - opmlFeed = this.DownloadOpmlFeed(uri); - - lock (this.cache) - { - this.cache[uri.AbsoluteUri] = opmlFeed; - } - } - - return opmlFeed; - } - - private static string PrepareTempDir() - { - string tempDir = null; - - try - { - string d = HttpContext.Current.Server.MapPath(Settings.CacheRoot + OPMLDir); - - if (!Directory.Exists(d)) - { - Directory.CreateDirectory(d); - } - - tempDir = d; - } - catch - { - // don't cache on disk if can't do it - } - - return tempDir; - } - - private static string GetTempFileNamePrefixFromUrl(Uri uri) - { - try - { - return string.Format("{0}_{1:x8}", uri.Host.Replace('.', '_'), uri.AbsolutePath.GetHashCode()); - } - catch - { - return "opml"; - } - } - - private Opml DownloadOpmlFeed(Uri uri) - { - // look for disk cache first - Opml opmlFeed = this.TryLoadFromDisk(uri); - - if (opmlFeed != null) - { - return opmlFeed; - } - - // May fail under partial trust - try - { - byte[] feed = new WebClient().DownloadData(uri.AbsoluteUri); - - var opmlDoc = new XmlDocument { XmlResolver = null }; - opmlDoc.Load(new MemoryStream(feed)); - opmlFeed = Opml.LoadFromXml(opmlDoc); - - opmlFeed.UtcExpiry = DateTime.UtcNow.AddMinutes(this.defaultTtlMinutes); - - // save to disk - this.TrySaveToDisk(opmlDoc, uri, opmlFeed.UtcExpiry); - } - catch - { - return new Opml(); - } - - return opmlFeed; - } - - private Opml TryLoadFromDisk(Uri uri) - { - if (this.directoryOnDisk == null) - { - return null; // no place to cache - } - - // look for all files matching the prefix - // looking for the one matching url that is not expired - // removing expired (or invalid) ones - string pattern = GetTempFileNamePrefixFromUrl(uri) + "_*.opml.resources"; - string[] files = Directory.GetFiles(this.directoryOnDisk, pattern, SearchOption.TopDirectoryOnly); - - foreach (string opmlFilename in files) - { - XmlDocument opmlDoc = null; - bool isOpmlFileValid = false; - DateTime utcExpiryFromOpmlFile = DateTime.MinValue; - string urlFromOpmlFile = null; - - try - { - opmlDoc = new XmlDocument { XmlResolver = null }; - opmlDoc.Load(opmlFilename); - - // look for special XML comment (before the root tag)' - // containing expiration and url - var comment = opmlDoc.DocumentElement.PreviousSibling as XmlComment; - - if (comment != null) - { - string c = comment.Value; - int i = c.IndexOf('@'); - long expiry; - - if (long.TryParse(c.Substring(0, i), out expiry)) - { - utcExpiryFromOpmlFile = DateTime.FromBinary(expiry); - urlFromOpmlFile = c.Substring(i + 1); - isOpmlFileValid = true; - } - } - } - catch - { - // error processing one file shouldn't stop processing other files - } - - // remove invalid or expired file - if (!isOpmlFileValid || utcExpiryFromOpmlFile < DateTime.UtcNow) - { - try - { - File.Delete(opmlFilename); - } - catch (Exception ex) - { - Logger.Error(ex); - } - - // try next file - continue; - } - - // match url - if (urlFromOpmlFile.Equals(uri.AbsoluteUri, StringComparison.OrdinalIgnoreCase)) - { - // found a good one - create DOM and set expiry (as found on disk) - Opml opmlFeed = Opml.LoadFromXml(opmlDoc); - opmlFeed.UtcExpiry = utcExpiryFromOpmlFile; - return opmlFeed; - } - } - - // not found - return null; - } - - private void TrySaveToDisk(XmlDocument doc, Uri uri, DateTime utcExpiry) - { - if (this.directoryOnDisk == null) - { - return; - } - - doc.InsertBefore(doc.CreateComment(string.Format("{0}@{1}", utcExpiry.ToBinary(), uri.AbsoluteUri)), doc.DocumentElement); - - string fileName = string.Format("{0}_{1:x8}.opml.resources", GetTempFileNamePrefixFromUrl(uri), Guid.NewGuid().ToString().GetHashCode()); - - try - { - doc.Save(Path.Combine(this.directoryOnDisk, fileName)); - } - catch - { - // can't save to disk - not a problem - } - } - } -} + /// Helper class that provides memory and disk caching of the downloaded feeds. + internal class OpmlDownloadManager + { + private const string OPMLDir = "/OPML/"; + private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(OpmlDownloadManager)); + private static readonly OpmlDownloadManager TheManager = new OpmlDownloadManager(); + + private readonly Dictionary cache; + private readonly int defaultTtlMinutes; + private readonly string directoryOnDisk; + + private OpmlDownloadManager() + { + // create in-memory cache + this.cache = new Dictionary(); + + this.defaultTtlMinutes = 60; + + // prepare disk directory + this.directoryOnDisk = PrepareTempDir(); + } + + /// Gets the OPML feed at a given . + /// The feed URI. + /// The OPML feed, or an empty OPML feed if there's an error loading it. + public static Opml GetOpmlFeed(Uri uri) + { + return TheManager.GetFeed(uri); + } + + /// Gets the OPML feed at a given . + /// The feed URI. + /// The OPML feed, or an empty OPML feed if there's an error loading it. + internal Opml GetFeed(Uri uri) + { + Opml opmlFeed = null; + + lock (this.cache) + { + if (this.cache.TryGetValue(uri.AbsoluteUri, out opmlFeed)) + { + if (DateTime.UtcNow > opmlFeed.UtcExpiry) + { + this.cache.Remove(uri.AbsoluteUri); + opmlFeed = null; + } + } + } + + if (opmlFeed == null) + { + opmlFeed = this.DownloadOpmlFeed(uri); + + lock (this.cache) + { + this.cache[uri.AbsoluteUri] = opmlFeed; + } + } + + return opmlFeed; + } + + private static string PrepareTempDir() + { + string tempDir = null; + + try + { + string d = HttpContext.Current.Server.MapPath(Settings.CacheRoot + OPMLDir); + + if (!Directory.Exists(d)) + { + Directory.CreateDirectory(d); + } + + tempDir = d; + } + catch + { + // don't cache on disk if can't do it + } + + return tempDir; + } + + private static string GetTempFileNamePrefixFromUrl(Uri uri) + { + try + { + return string.Format("{0}_{1:x8}", uri.Host.Replace('.', '_'), uri.AbsolutePath.GetHashCode()); + } + catch + { + return "opml"; + } + } + + private Opml DownloadOpmlFeed(Uri uri) + { + // look for disk cache first + Opml opmlFeed = this.TryLoadFromDisk(uri); + + if (opmlFeed != null) + { + return opmlFeed; + } + + // May fail under partial trust + try + { + byte[] feed = new WebClient().DownloadData(uri.AbsoluteUri); + + var opmlDoc = new XmlDocument { XmlResolver = null }; + opmlDoc.Load(new MemoryStream(feed)); + opmlFeed = Opml.LoadFromXml(opmlDoc); + + opmlFeed.UtcExpiry = DateTime.UtcNow.AddMinutes(this.defaultTtlMinutes); + + // save to disk + this.TrySaveToDisk(opmlDoc, uri, opmlFeed.UtcExpiry); + } + catch + { + return new Opml(); + } + + return opmlFeed; + } + + private Opml TryLoadFromDisk(Uri uri) + { + if (this.directoryOnDisk == null) + { + return null; // no place to cache + } + + // look for all files matching the prefix + // looking for the one matching url that is not expired + // removing expired (or invalid) ones + string pattern = GetTempFileNamePrefixFromUrl(uri) + "_*.opml.resources"; + string[] files = Directory.GetFiles(this.directoryOnDisk, pattern, SearchOption.TopDirectoryOnly); + + foreach (string opmlFilename in files) + { + XmlDocument opmlDoc = null; + bool isOpmlFileValid = false; + DateTime utcExpiryFromOpmlFile = DateTime.MinValue; + string urlFromOpmlFile = null; + + try + { + opmlDoc = new XmlDocument { XmlResolver = null }; + opmlDoc.Load(opmlFilename); + + // look for special XML comment (before the root tag)' + // containing expiration and url + var comment = opmlDoc.DocumentElement.PreviousSibling as XmlComment; + + if (comment != null) + { + string c = comment.Value; + int i = c.IndexOf('@'); + long expiry; + + if (long.TryParse(c.Substring(0, i), out expiry)) + { + utcExpiryFromOpmlFile = DateTime.FromBinary(expiry); + urlFromOpmlFile = c.Substring(i + 1); + isOpmlFileValid = true; + } + } + } + catch + { + // error processing one file shouldn't stop processing other files + } + + // remove invalid or expired file + if (!isOpmlFileValid || utcExpiryFromOpmlFile < DateTime.UtcNow) + { + try + { + File.Delete(opmlFilename); + } + catch (Exception ex) + { + Logger.Error(ex); + } + + // try next file + continue; + } + + // match url + if (urlFromOpmlFile.Equals(uri.AbsoluteUri, StringComparison.OrdinalIgnoreCase)) + { + // found a good one - create DOM and set expiry (as found on disk) + Opml opmlFeed = Opml.LoadFromXml(opmlDoc); + opmlFeed.UtcExpiry = utcExpiryFromOpmlFile; + return opmlFeed; + } + } + + // not found + return null; + } + + private void TrySaveToDisk(XmlDocument doc, Uri uri, DateTime utcExpiry) + { + if (this.directoryOnDisk == null) + { + return; + } + + doc.InsertBefore(doc.CreateComment(string.Format("{0}@{1}", utcExpiry.ToBinary(), uri.AbsoluteUri)), doc.DocumentElement); + + string fileName = string.Format("{0}_{1:x8}.opml.resources", GetTempFileNamePrefixFromUrl(uri), Guid.NewGuid().ToString().GetHashCode()); + + try + { + doc.Save(Path.Combine(this.directoryOnDisk, fileName)); + } + catch + { + // can't save to disk - not a problem + } + } + } +} diff --git a/DNN Platform/Syndication/OPML/OpmlOutline.cs b/DNN Platform/Syndication/OPML/OpmlOutline.cs index ffb06dc6e50..93955a8644e 100644 --- a/DNN Platform/Syndication/OPML/OpmlOutline.cs +++ b/DNN Platform/Syndication/OPML/OpmlOutline.cs @@ -6,30 +6,19 @@ namespace DotNetNuke.Services.Syndication using System; using System.Xml; - /// Class for managing an OPML feed outline. + /// Class for managing an OPML feed outline. public class OpmlOutline { - private string category = string.Empty; - private DateTime created = DateTime.MinValue; - private string description = string.Empty; - private string language = string.Empty; - private string text = string.Empty; - private string title = string.Empty; - private string type = "rss"; - + /// Initializes a new instance of the class. public OpmlOutline() { this.Outlines = new OpmlOutlines(); } - public string Version - { - get - { - return "2.0"; - } - } + /// Gets the OPML outline version. + public string Version => "2.0"; + /// Gets the OPML outline as an . public XmlElement ToXml { get @@ -99,107 +88,43 @@ public XmlElement ToXml } } - public string Description - { - get - { - return this.description; - } - - set - { - this.description = value; - } - } + /// Gets or sets the OPML outline description. + public string Description { get; set; } = string.Empty; - public string Title - { - get - { - return this.title; - } - - set - { - this.title = value; - } - } - - public string Type - { - get - { - return this.type; - } - - set - { - this.type = value; - } - } + /// Gets or sets the OPML outline title. + public string Title { get; set; } = string.Empty; - public string Text - { - get - { - return this.text; - } + /// Gets or sets the OPML outline type. + public string Type { get; set; } = "rss"; - set - { - this.text = value; - } - } + /// Gets or sets the OPML outline text. + public string Text { get; set; } = string.Empty; + /// Gets or sets the OPML outline's HTML URL. public Uri HtmlUrl { get; set; } + /// Gets or sets the OPML outline's XML URL. public Uri XmlUrl { get; set; } + /// Gets or sets the OPML outline's URL. public Uri Url { get; set; } - public DateTime Created - { - get - { - return this.created; - } - - set - { - this.created = value; - } - } + /// Gets or sets the creation date for the OPML outline. + public DateTime Created { get; set; } = DateTime.MinValue; + /// Gets or sets a value indicating whether this OPML outline is a comment. public bool IsComment { get; set; } + /// Gets or sets a value indicating whether this OPML outline is a breakpoint. public bool IsBreakpoint { get; set; } - public string Category - { - get - { - return this.category; - } - - set - { - this.category = value; - } - } + /// Gets or sets the OPML outline category. + public string Category { get; set; } = string.Empty; - public string Language - { - get - { - return this.language; - } - - set - { - this.language = value; - } - } + /// Gets or sets the OPML outline language. + public string Language { get; set; } = string.Empty; + /// Gets or sets the outlines contained in this OPML outline. public OpmlOutlines Outlines { get; set; } } } diff --git a/DNN Platform/Syndication/OPML/OpmlOutlines.cs b/DNN Platform/Syndication/OPML/OpmlOutlines.cs index 8e7c178b80f..1c7c1385d92 100644 --- a/DNN Platform/Syndication/OPML/OpmlOutlines.cs +++ b/DNN Platform/Syndication/OPML/OpmlOutlines.cs @@ -1,11 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information - namespace DotNetNuke.Services.Syndication { using System.Collections.Generic; + /// A collection of OPML outlines. public class OpmlOutlines : List { } diff --git a/DNN Platform/Syndication/RSS/GenericRssChannel.cs b/DNN Platform/Syndication/RSS/GenericRssChannel.cs index 741fc2a782b..f0986d998fb 100644 --- a/DNN Platform/Syndication/RSS/GenericRssChannel.cs +++ b/DNN Platform/Syndication/RSS/GenericRssChannel.cs @@ -1,79 +1,75 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Services.Syndication -{ - using System.Collections; - using System.Collections.Generic; +namespace DotNetNuke.Services.Syndication +{ + using System.Collections; + using System.Collections.Generic; using System.Xml; - /// Class to consume (or create) a channel in a late-bound way. - public sealed class GenericRssChannel : RssChannelBase - { - public new Dictionary Attributes - { - get - { - return base.Attributes; - } - } - - public GenericRssElement Image - { - get - { - return this.GetImage(); - } - } - - public string this[string attributeName] - { - get - { - return this.GetAttributeValue(attributeName); - } - - set - { - this.Attributes[attributeName] = value; - } - } - - public static GenericRssChannel LoadChannel(string url) - { - var channel = new GenericRssChannel(); - channel.LoadFromUrl(url); - return channel; - } - - public static GenericRssChannel LoadChannel(XmlDocument doc) - { - var channel = new GenericRssChannel(); - channel.LoadFromXml(doc); - return channel; - } - - // Select method for programmatic databinding - public IEnumerable SelectItems() - { - return this.SelectItems(-1); - } - - public IEnumerable SelectItems(int maxItems) - { - var data = new ArrayList(); - - foreach (GenericRssElement element in this.Items) - { - if (maxItems > 0 && data.Count >= maxItems) - { - break; - } - - data.Add(new RssElementCustomTypeDescriptor(element.Attributes)); - } - - return data; - } - } -} + /// Class to consume (or create) a channel in a late-bound way. + public sealed class GenericRssChannel : RssChannelBase + { + /// + public new Dictionary Attributes => base.Attributes; + + /// Gets the channel image. + public GenericRssElement Image => this.GetImage(); + + /// Gets or sets the channel's attributes. + /// The attribute name. + public string this[string attributeName] + { + get => this.GetAttributeValue(attributeName); + set => this.Attributes[attributeName] = value; + } + + /// Loads an RSS channel from a . + /// The channel's URL. + /// A new instance. + public static GenericRssChannel LoadChannel(string url) + { + var channel = new GenericRssChannel(); + channel.LoadFromUrl(url); + return channel; + } + + /// Loads an RSS channel from a . + /// The XML document. + /// A new instance. + public static GenericRssChannel LoadChannel(XmlDocument doc) + { + var channel = new GenericRssChannel(); + channel.LoadFromXml(doc); + return channel; + } + + /// Get a sequence of the channel's items. + /// A sequence of instances. + /// This method is for programmatic data-binding. + public IEnumerable SelectItems() + { + return this.SelectItems(-1); + } + + /// Get a sequence of the channel's items. + /// The maximum number of items to include, or include all items if the value is not positive. + /// A sequence of instances. + public IEnumerable SelectItems(int maxItems) + { + var data = new ArrayList(); + + foreach (GenericRssElement element in this.Items) + { + if (maxItems > 0 && data.Count >= maxItems) + { + break; + } + + data.Add(new RssElementCustomTypeDescriptor(element.Attributes)); + } + + return data; + } + } +} diff --git a/DNN Platform/Syndication/RSS/GenericRssElement.cs b/DNN Platform/Syndication/RSS/GenericRssElement.cs index bab4925a498..62b37926482 100644 --- a/DNN Platform/Syndication/RSS/GenericRssElement.cs +++ b/DNN Platform/Syndication/RSS/GenericRssElement.cs @@ -1,32 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Services.Syndication -{ +namespace DotNetNuke.Services.Syndication +{ using System.Collections.Generic; - /// Late-bound RSS element (used for late bound item and image). - public sealed class GenericRssElement : RssElementBase - { - public new Dictionary Attributes - { - get - { - return base.Attributes; - } - } - - public string this[string attributeName] - { - get - { - return this.GetAttributeValue(attributeName); - } - - set - { - this.Attributes[attributeName] = value; - } - } - } -} + /// Late-bound RSS element (used for late bound item and image). + public sealed class GenericRssElement : RssElementBase + { + /// + public new Dictionary Attributes => base.Attributes; + + /// Gets or sets the element's attributes. + /// The attribute name. + public string this[string attributeName] + { + get => this.GetAttributeValue(attributeName); + set => this.Attributes[attributeName] = value; + } + } +} diff --git a/DNN Platform/Syndication/RSS/RssChannelBase.cs b/DNN Platform/Syndication/RSS/RssChannelBase.cs index e1302aa0112..9c3e1396cb7 100644 --- a/DNN Platform/Syndication/RSS/RssChannelBase.cs +++ b/DNN Platform/Syndication/RSS/RssChannelBase.cs @@ -13,31 +13,24 @@ public abstract class RssChannelBase : RssElementBa where TRssItemType : RssElementBase, new() where TRssImageType : RssElementBase, new() { - private readonly List items = new List(); private TRssImageType image; - private string url; - public List Items - { - get - { - return this.items; - } - } + /// Gets the channel items. + public List Items { get; } = new List(); - internal string Url - { - get - { - return this.url; - } - } + /// Gets the channel URL. + internal string Url { get; private set; } + /// Gets the channel as an XML document. + /// A new instance. public XmlDocument SaveAsXml() { return this.SaveAsXml(RssXmlHelper.CreateEmptyRssXml()); } + /// Gets the channel as an XML document. + /// An empty to save the channel contents into. + /// The passed in with the channel contents added. public XmlDocument SaveAsXml(XmlDocument emptyRssXml) { XmlDocument doc = emptyRssXml; @@ -48,7 +41,7 @@ public XmlDocument SaveAsXml(XmlDocument emptyRssXml) RssXmlHelper.SaveRssElementAsXml(channelNode, this.image, "image"); } - foreach (TRssItemType item in this.items) + foreach (TRssItemType item in this.Items) { RssXmlHelper.SaveRssElementAsXml(channelNode, item, "item"); } @@ -56,6 +49,8 @@ public XmlDocument SaveAsXml(XmlDocument emptyRssXml) return doc; } + /// Loads the contents from the into this channel. + /// The DOM to load from. internal void LoadFromDom(RssChannelDom dom) { // channel attributes @@ -74,10 +69,12 @@ internal void LoadFromDom(RssChannelDom dom) { var item = new TRssItemType(); item.SetAttributes(i); - this.items.Add(item); + this.Items.Add(item); } } + /// Loads a channel from a into this channel. + /// The URL to load the channel from. protected void LoadFromUrl(string url) { // download the feed @@ -87,9 +84,11 @@ protected void LoadFromUrl(string url) this.LoadFromDom(dom); // remember the url - this.url = url; + this.Url = url; } + /// Loads a channel from a into this channel. + /// The XML to load the channel from. protected void LoadFromXml(XmlDocument doc) { // parse XML @@ -99,6 +98,8 @@ protected void LoadFromXml(XmlDocument doc) this.LoadFromDom(dom); } + /// Gets the channel image. + /// An instance. protected TRssImageType GetImage() { if (this.image == null) diff --git a/DNN Platform/Syndication/RSS/RssChannelDom.cs b/DNN Platform/Syndication/RSS/RssChannelDom.cs index 0bb058705c8..1504c8b0315 100644 --- a/DNN Platform/Syndication/RSS/RssChannelDom.cs +++ b/DNN Platform/Syndication/RSS/RssChannelDom.cs @@ -1,62 +1,43 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Services.Syndication -{ - using System; +namespace DotNetNuke.Services.Syndication +{ + using System; using System.Collections.Generic; - /// Internal representation of parsed RSS channel. - internal class RssChannelDom - { - private readonly Dictionary channel; - private readonly Dictionary image; - private readonly List> items; - private DateTime utcExpiry; - - internal RssChannelDom(Dictionary channel, Dictionary image, List> items) - { - this.channel = channel; - this.image = image; - this.items = items; - this.utcExpiry = DateTime.MaxValue; - } - - internal Dictionary Channel - { - get - { - return this.channel; - } - } - - internal Dictionary Image - { - get - { - return this.image; - } - } - - internal List> Items - { - get - { - return this.items; - } - } - - internal DateTime UtcExpiry - { - get - { - return this.utcExpiry; - } - } - - internal void SetExpiry(DateTime utcExpiry) - { - this.utcExpiry = utcExpiry; - } - } -} + /// Internal representation of parsed RSS channel. + internal class RssChannelDom + { + /// Initializes a new instance of the class. + /// The channel attributes. + /// The image attributes. + /// The items. + internal RssChannelDom(Dictionary channel, Dictionary image, List> items) + { + this.Channel = channel; + this.Image = image; + this.Items = items; + this.UtcExpiry = DateTime.MaxValue; + } + + /// Gets the channel attributes. + internal Dictionary Channel { get; } + + /// Gets the image attributes. + internal Dictionary Image { get; } + + /// Gets the items. + internal List> Items { get; } + + /// Gets the expiration in UTC. + internal DateTime UtcExpiry { get; private set; } + + /// Sets the expiration. + /// The expiration on UTC. + internal void SetExpiry(DateTime utcExpiry) + { + this.UtcExpiry = utcExpiry; + } + } +} diff --git a/DNN Platform/Syndication/RSS/RssDataSource.cs b/DNN Platform/Syndication/RSS/RssDataSource.cs index 9887f5fefdb..696d1523333 100644 --- a/DNN Platform/Syndication/RSS/RssDataSource.cs +++ b/DNN Platform/Syndication/RSS/RssDataSource.cs @@ -6,7 +6,7 @@ namespace DotNetNuke.Services.Syndication using System.ComponentModel; using System.Web.UI; - /// RSS data source control implementation, including the designer. + /// RSS data source control implementation, including the designer. [DefaultProperty("Url")] public class RssDataSource : DataSourceControl { @@ -14,6 +14,7 @@ public class RssDataSource : DataSourceControl private RssDataSourceView itemsView; private string url; + /// Gets the channel. public GenericRssChannel Channel { get @@ -34,8 +35,10 @@ public GenericRssChannel Channel } } + /// Gets or sets the maximum number of items. public int MaxItems { get; set; } + /// Gets or sets the URL. public string Url { get diff --git a/DNN Platform/Syndication/RSS/RssDataSourceView.cs b/DNN Platform/Syndication/RSS/RssDataSourceView.cs index 5717d2b2b0c..2b899149589 100644 --- a/DNN Platform/Syndication/RSS/RssDataSourceView.cs +++ b/DNN Platform/Syndication/RSS/RssDataSourceView.cs @@ -1,19 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information - namespace DotNetNuke.Services.Syndication { using System.Collections; using System.Web.UI; + /// An RSS . public class RssDataSourceView : DataSourceView { private readonly RssDataSource owner; /// Initializes a new instance of the class. - /// - /// + /// internal RssDataSourceView(RssDataSource owner, string viewName) : base(owner, viewName) { diff --git a/DNN Platform/Syndication/RSS/RssDownloadManager.cs b/DNN Platform/Syndication/RSS/RssDownloadManager.cs index 0577238a1e6..7585d372928 100644 --- a/DNN Platform/Syndication/RSS/RssDownloadManager.cs +++ b/DNN Platform/Syndication/RSS/RssDownloadManager.cs @@ -1,256 +1,259 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Services.Syndication -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Net; - using System.Web; - using System.Xml; +namespace DotNetNuke.Services.Syndication +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Web; + using System.Xml; using DotNetNuke.Instrumentation; - /// Helper class that provides memory and disk caching of the downloaded feeds. - internal class RssDownloadManager - { - private const string RSSDir = "/RSS/"; - private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(RssDownloadManager)); - private static readonly RssDownloadManager TheManager = new RssDownloadManager(); - - private readonly Dictionary cache; - private readonly int defaultTtlMinutes; - private readonly string directoryOnDisk; - - private RssDownloadManager() - { - // create in-memory cache - this.cache = new Dictionary(); - - this.defaultTtlMinutes = 2; - - // prepare disk directory - this.directoryOnDisk = PrepareTempDir(); - } - - public static RssChannelDom GetChannel(string url) - { - return TheManager.GetChannelDom(url); - } - - private static int GetTtlFromString(string ttlString, int defaultTtlMinutes) - { - if (!string.IsNullOrEmpty(ttlString)) - { - int ttlMinutes; - if (int.TryParse(ttlString, out ttlMinutes)) - { - if (ttlMinutes >= 0) - { - return ttlMinutes; - } - } - } - - return defaultTtlMinutes; - } - - private static string PrepareTempDir() - { - string tempDir = null; - - try - { - string d = HttpContext.Current.Server.MapPath(Settings.CacheRoot + RSSDir); - - if (!Directory.Exists(d)) - { - Directory.CreateDirectory(d); - } - - tempDir = d; - } - catch - { - // don't cache on disk if can't do it - } - - return tempDir; - } - - private static string GetTempFileNamePrefixFromUrl(string url) - { - try - { - var uri = new Uri(url); - return string.Format("{0}_{1:x8}", uri.Host.Replace('.', '_'), uri.AbsolutePath.GetHashCode()); - } - catch - { - return "rss"; - } - } - - private RssChannelDom DownloadChannelDom(string url) - { - // look for disk cache first - RssChannelDom dom = this.TryLoadFromDisk(url); - - if (dom != null) - { - return dom; - } - - // download the feed - byte[] feed = new WebClient().DownloadData(url); - - // parse it as XML - var doc = new XmlDocument { XmlResolver = null }; - doc.Load(new MemoryStream(feed)); - - // parse into DOM - dom = RssXmlHelper.ParseChannelXml(doc); - - // set expiry - string ttlString = null; - dom.Channel.TryGetValue("ttl", out ttlString); - int ttlMinutes = GetTtlFromString(ttlString, this.defaultTtlMinutes); - DateTime utcExpiry = DateTime.UtcNow.AddMinutes(ttlMinutes); - dom.SetExpiry(utcExpiry); - - // save to disk - this.TrySaveToDisk(doc, url, utcExpiry); - - return dom; - } - - private RssChannelDom TryLoadFromDisk(string url) - { - if (this.directoryOnDisk == null) - { - return null; // no place to cache - } - - // look for all files matching the prefix - // looking for the one matching url that is not expired - // removing expired (or invalid) ones - string pattern = GetTempFileNamePrefixFromUrl(url) + "_*.rss.resources"; - string[] files = Directory.GetFiles(this.directoryOnDisk, pattern, SearchOption.TopDirectoryOnly); - - foreach (string rssFilename in files) - { - XmlDocument rssDoc = null; - bool isRssFileValid = false; - DateTime utcExpiryFromRssFile = DateTime.MinValue; - string urlFromRssFile = null; - - try - { - rssDoc = new XmlDocument { XmlResolver = null }; - rssDoc.Load(rssFilename); - - // look for special XML comment (before the root tag)' - // containing expiration and url - var comment = rssDoc.DocumentElement.PreviousSibling as XmlComment; - - if (comment != null) - { - string c = comment.Value; - int i = c.IndexOf('@'); - long expiry; - - if (long.TryParse(c.Substring(0, i), out expiry)) - { - utcExpiryFromRssFile = DateTime.FromBinary(expiry); - urlFromRssFile = c.Substring(i + 1); - isRssFileValid = true; - } - } - } - catch - { - // error processing one file shouldn't stop processing other files - } - - // remove invalid or expired file - if (!isRssFileValid || utcExpiryFromRssFile < DateTime.UtcNow) - { - try - { - File.Delete(rssFilename); - } - catch (Exception ex) - { - Logger.Error(ex); - } - - // try next file - continue; - } - - // match url - if (urlFromRssFile == url) - { - // found a good one - create DOM and set expiry (as found on disk) - RssChannelDom dom = RssXmlHelper.ParseChannelXml(rssDoc); - dom.SetExpiry(utcExpiryFromRssFile); - return dom; - } - } - - // not found - return null; - } - - private void TrySaveToDisk(XmlDocument doc, string url, DateTime utcExpiry) - { - if (this.directoryOnDisk == null) - { - return; - } - - doc.InsertBefore(doc.CreateComment(string.Format("{0}@{1}", utcExpiry.ToBinary(), url)), doc.DocumentElement); - - string fileName = string.Format("{0}_{1:x8}.rss.resources", GetTempFileNamePrefixFromUrl(url), Guid.NewGuid().ToString().GetHashCode()); - - try - { - doc.Save(Path.Combine(this.directoryOnDisk, fileName)); - } - catch - { - // can't save to disk - not a problem - } - } - - private RssChannelDom GetChannelDom(string url) - { - RssChannelDom dom = null; - - lock (this.cache) - { - if (this.cache.TryGetValue(url, out dom)) - { - if (DateTime.UtcNow > dom.UtcExpiry) - { - this.cache.Remove(url); - dom = null; - } - } - } - - if (dom == null) - { - dom = this.DownloadChannelDom(url); - - lock (this.cache) - { - this.cache[url] = dom; - } - } - - return dom; - } - } -} + /// Helper class that provides memory and disk caching of the downloaded feeds. + internal class RssDownloadManager + { + private const string RSSDir = "/RSS/"; + private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(RssDownloadManager)); + private static readonly RssDownloadManager TheManager = new RssDownloadManager(); + + private readonly Dictionary cache; + private readonly int defaultTtlMinutes; + private readonly string directoryOnDisk; + + private RssDownloadManager() + { + // create in-memory cache + this.cache = new Dictionary(); + + this.defaultTtlMinutes = 2; + + // prepare disk directory + this.directoryOnDisk = PrepareTempDir(); + } + + /// Gets the RSS channel at the given . + /// The URL. + /// The RSS feed, or an empty RSS feed if there's an error loading it. + public static RssChannelDom GetChannel(string url) + { + return TheManager.GetChannelDom(url); + } + + private static int GetTtlFromString(string ttlString, int defaultTtlMinutes) + { + if (!string.IsNullOrEmpty(ttlString)) + { + int ttlMinutes; + if (int.TryParse(ttlString, out ttlMinutes)) + { + if (ttlMinutes >= 0) + { + return ttlMinutes; + } + } + } + + return defaultTtlMinutes; + } + + private static string PrepareTempDir() + { + string tempDir = null; + + try + { + string d = HttpContext.Current.Server.MapPath(Settings.CacheRoot + RSSDir); + + if (!Directory.Exists(d)) + { + Directory.CreateDirectory(d); + } + + tempDir = d; + } + catch + { + // don't cache on disk if can't do it + } + + return tempDir; + } + + private static string GetTempFileNamePrefixFromUrl(string url) + { + try + { + var uri = new Uri(url); + return string.Format("{0}_{1:x8}", uri.Host.Replace('.', '_'), uri.AbsolutePath.GetHashCode()); + } + catch + { + return "rss"; + } + } + + private RssChannelDom DownloadChannelDom(string url) + { + // look for disk cache first + RssChannelDom dom = this.TryLoadFromDisk(url); + + if (dom != null) + { + return dom; + } + + // download the feed + byte[] feed = new WebClient().DownloadData(url); + + // parse it as XML + var doc = new XmlDocument { XmlResolver = null }; + doc.Load(new MemoryStream(feed)); + + // parse into DOM + dom = RssXmlHelper.ParseChannelXml(doc); + + // set expiry + string ttlString = null; + dom.Channel.TryGetValue("ttl", out ttlString); + int ttlMinutes = GetTtlFromString(ttlString, this.defaultTtlMinutes); + DateTime utcExpiry = DateTime.UtcNow.AddMinutes(ttlMinutes); + dom.SetExpiry(utcExpiry); + + // save to disk + this.TrySaveToDisk(doc, url, utcExpiry); + + return dom; + } + + private RssChannelDom TryLoadFromDisk(string url) + { + if (this.directoryOnDisk == null) + { + return null; // no place to cache + } + + // look for all files matching the prefix + // looking for the one matching url that is not expired + // removing expired (or invalid) ones + string pattern = GetTempFileNamePrefixFromUrl(url) + "_*.rss.resources"; + string[] files = Directory.GetFiles(this.directoryOnDisk, pattern, SearchOption.TopDirectoryOnly); + + foreach (string rssFilename in files) + { + XmlDocument rssDoc = null; + bool isRssFileValid = false; + DateTime utcExpiryFromRssFile = DateTime.MinValue; + string urlFromRssFile = null; + + try + { + rssDoc = new XmlDocument { XmlResolver = null }; + rssDoc.Load(rssFilename); + + // look for special XML comment (before the root tag)' + // containing expiration and url + var comment = rssDoc.DocumentElement.PreviousSibling as XmlComment; + + if (comment != null) + { + string c = comment.Value; + int i = c.IndexOf('@'); + long expiry; + + if (long.TryParse(c.Substring(0, i), out expiry)) + { + utcExpiryFromRssFile = DateTime.FromBinary(expiry); + urlFromRssFile = c.Substring(i + 1); + isRssFileValid = true; + } + } + } + catch + { + // error processing one file shouldn't stop processing other files + } + + // remove invalid or expired file + if (!isRssFileValid || utcExpiryFromRssFile < DateTime.UtcNow) + { + try + { + File.Delete(rssFilename); + } + catch (Exception ex) + { + Logger.Error(ex); + } + + // try next file + continue; + } + + // match url + if (urlFromRssFile == url) + { + // found a good one - create DOM and set expiry (as found on disk) + RssChannelDom dom = RssXmlHelper.ParseChannelXml(rssDoc); + dom.SetExpiry(utcExpiryFromRssFile); + return dom; + } + } + + // not found + return null; + } + + private void TrySaveToDisk(XmlDocument doc, string url, DateTime utcExpiry) + { + if (this.directoryOnDisk == null) + { + return; + } + + doc.InsertBefore(doc.CreateComment(string.Format("{0}@{1}", utcExpiry.ToBinary(), url)), doc.DocumentElement); + + string fileName = string.Format("{0}_{1:x8}.rss.resources", GetTempFileNamePrefixFromUrl(url), Guid.NewGuid().ToString().GetHashCode()); + + try + { + doc.Save(Path.Combine(this.directoryOnDisk, fileName)); + } + catch + { + // can't save to disk - not a problem + } + } + + private RssChannelDom GetChannelDom(string url) + { + RssChannelDom dom = null; + + lock (this.cache) + { + if (this.cache.TryGetValue(url, out dom)) + { + if (DateTime.UtcNow > dom.UtcExpiry) + { + this.cache.Remove(url); + dom = null; + } + } + } + + if (dom == null) + { + dom = this.DownloadChannelDom(url); + + lock (this.cache) + { + this.cache[url] = dom; + } + } + + return dom; + } + } +} diff --git a/DNN Platform/Syndication/RSS/RssElementBase.cs b/DNN Platform/Syndication/RSS/RssElementBase.cs index b72ab787b3d..1b39069e3f0 100644 --- a/DNN Platform/Syndication/RSS/RssElementBase.cs +++ b/DNN Platform/Syndication/RSS/RssElementBase.cs @@ -1,51 +1,50 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Services.Syndication -{ - using System; +namespace DotNetNuke.Services.Syndication +{ + using System; using System.Collections.Generic; - /// - /// The base class for all RSS elements (item, image, channel) - /// has collection of attributes. - /// - public abstract class RssElementBase - { - private Dictionary attributes = new Dictionary(StringComparer.OrdinalIgnoreCase); - - protected internal Dictionary Attributes - { - get - { - return this.attributes; - } - } - - public virtual void SetDefaults() - { - } - - internal void SetAttributes(Dictionary attributes) - { - this.attributes = attributes; - } - - protected string GetAttributeValue(string attributeName) - { - string attributeValue; - - if (!this.attributes.TryGetValue(attributeName, out attributeValue)) - { - attributeValue = string.Empty; - } - - return attributeValue; - } - - protected void SetAttributeValue(string attributeName, string attributeValue) - { - this.attributes[attributeName] = attributeValue; - } - } -} + /// The base class for all RSS elements (item, image, channel) has collection of attributes. + public abstract class RssElementBase + { + /// Gets the attributes. + protected internal Dictionary Attributes { get; private set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// When overridden in a derived class, initializes the attributes to the defaults. + public virtual void SetDefaults() + { + } + + /// Sets the element attributes. + /// The attributes. + internal void SetAttributes(Dictionary attributes) + { + this.Attributes = attributes; + } + + /// Gets the attribute value. + /// The attribute name. + /// The value or . + protected string GetAttributeValue(string attributeName) + { + string attributeValue; + + if (!this.Attributes.TryGetValue(attributeName, out attributeValue)) + { + attributeValue = string.Empty; + } + + return attributeValue; + } + + /// Sets the value of an attribute. + /// The attribute name. + /// The attribute value. + protected void SetAttributeValue(string attributeName, string attributeValue) + { + this.Attributes[attributeName] = attributeValue; + } + } +} diff --git a/DNN Platform/Syndication/RSS/RssElementCustomTypeDescriptor.cs b/DNN Platform/Syndication/RSS/RssElementCustomTypeDescriptor.cs index 1be24d5aa03..fcab2e6059f 100644 --- a/DNN Platform/Syndication/RSS/RssElementCustomTypeDescriptor.cs +++ b/DNN Platform/Syndication/RSS/RssElementCustomTypeDescriptor.cs @@ -1,174 +1,170 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Services.Syndication -{ - using System; - using System.Collections.Generic; +namespace DotNetNuke.Services.Syndication +{ + using System; + using System.Collections.Generic; using System.ComponentModel; - /// Helper class to enable the data binding logic generate column names at runtime. - internal class RssElementCustomTypeDescriptor : ICustomTypeDescriptor - { + /// Helper class to enable the data binding logic generate column names at runtime. + internal class RssElementCustomTypeDescriptor : ICustomTypeDescriptor + { private readonly Dictionary attributes; /// Initializes a new instance of the class. - /// - public RssElementCustomTypeDescriptor(Dictionary attributes) - { - this.attributes = attributes; + /// The attributes. + public RssElementCustomTypeDescriptor(Dictionary attributes) + { + this.attributes = attributes; } /// - AttributeCollection ICustomTypeDescriptor.GetAttributes() - { - return AttributeCollection.Empty; + AttributeCollection ICustomTypeDescriptor.GetAttributes() + { + return AttributeCollection.Empty; } /// - string ICustomTypeDescriptor.GetClassName() - { - return this.GetType().Name; + string ICustomTypeDescriptor.GetClassName() + { + return this.GetType().Name; } /// - string ICustomTypeDescriptor.GetComponentName() - { - return null; + string ICustomTypeDescriptor.GetComponentName() + { + return null; } /// - TypeConverter ICustomTypeDescriptor.GetConverter() - { - return null; + TypeConverter ICustomTypeDescriptor.GetConverter() + { + return null; } /// - EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() - { - return null; + EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() + { + return null; } /// - PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() - { - return null; + PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() + { + return null; } /// - object ICustomTypeDescriptor.GetEditor(Type editorBaseType) - { - return null; + object ICustomTypeDescriptor.GetEditor(Type editorBaseType) + { + return null; } /// - EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) - { - return null; + EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) + { + return null; } /// - EventDescriptorCollection ICustomTypeDescriptor.GetEvents() - { - return null; + EventDescriptorCollection ICustomTypeDescriptor.GetEvents() + { + return null; } /// - PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) - { - return this.GetPropertyDescriptors(); + PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) + { + return this.GetPropertyDescriptors(); } /// - PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() - { - return this.GetPropertyDescriptors(); + PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() + { + return this.GetPropertyDescriptors(); } /// - object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) - { - return (pd is RssElementCustomPropertyDescriptor) ? this : null; - } - - private PropertyDescriptorCollection GetPropertyDescriptors() - { - var propertyDescriptors = new PropertyDescriptor[this.attributes.Count]; - int i = 0; - - foreach (KeyValuePair a in this.attributes) - { - propertyDescriptors[i++] = new RssElementCustomPropertyDescriptor(a.Key); - } - - return new PropertyDescriptorCollection(propertyDescriptors); - } - - private class RssElementCustomPropertyDescriptor : PropertyDescriptor - { - public RssElementCustomPropertyDescriptor(string propertyName) - : base(propertyName, null) - { - } - - public override Type ComponentType - { - get - { - return typeof(RssElementCustomTypeDescriptor); - } - } - - public override bool IsReadOnly - { - get - { - return true; - } - } - - public override Type PropertyType - { - get - { - return typeof(string); - } - } - - public override bool CanResetValue(object o) - { - return false; - } - - public override void ResetValue(object o) - { - } - - public override void SetValue(object o, object value) - { - } - - public override bool ShouldSerializeValue(object o) - { - return true; - } - - public override object GetValue(object o) - { - var element = o as RssElementCustomTypeDescriptor; - - if (element != null) - { - string propertyValue; - - if (element.attributes.TryGetValue(this.Name, out propertyValue)) - { - return propertyValue; - } - } - - return string.Empty; - } - } - } -} + object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) + { + return (pd is RssElementCustomPropertyDescriptor) ? this : null; + } + + private PropertyDescriptorCollection GetPropertyDescriptors() + { + var propertyDescriptors = new PropertyDescriptor[this.attributes.Count]; + int i = 0; + + foreach (KeyValuePair a in this.attributes) + { + propertyDescriptors[i++] = new RssElementCustomPropertyDescriptor(a.Key); + } + + return new PropertyDescriptorCollection(propertyDescriptors); + } + + private class RssElementCustomPropertyDescriptor : PropertyDescriptor + { + public RssElementCustomPropertyDescriptor(string propertyName) + : base(propertyName, null) + { + } + + public override Type ComponentType + { + get + { + return typeof(RssElementCustomTypeDescriptor); + } + } + + public override bool IsReadOnly + { + get + { + return true; + } + } + + public override Type PropertyType + { + get + { + return typeof(string); + } + } + + public override bool CanResetValue(object o) + { + return false; + } + + public override void ResetValue(object o) + { + } + + public override void SetValue(object o, object value) + { + } + + public override bool ShouldSerializeValue(object o) + { + return true; + } + + public override object GetValue(object o) + { + if (o is RssElementCustomTypeDescriptor element) + { + if (element.attributes.TryGetValue(this.Name, out var propertyValue)) + { + return propertyValue; + } + } + + return string.Empty; + } + } + } +} diff --git a/DNN Platform/Syndication/RSS/RssHttpHandlerBase.cs b/DNN Platform/Syndication/RSS/RssHttpHandlerBase.cs index 5c8bd239de1..eed458907aa 100644 --- a/DNN Platform/Syndication/RSS/RssHttpHandlerBase.cs +++ b/DNN Platform/Syndication/RSS/RssHttpHandlerBase.cs @@ -7,8 +7,14 @@ namespace DotNetNuke.Services.Syndication using System.Web; using System.Xml; + /// A function which handles the event. + /// The event source, i.e. the instance. + /// Empty event args. public delegate void InitEventHandler(object source, EventArgs e); + /// A function which handles the event. + /// The event source, i.e. the instance. + /// Empty event args. public delegate void PreRenderEventHandler(object source, EventArgs e); /// Base class for RssHttpHandler - Generic handler and strongly typed ones are derived from it. @@ -20,37 +26,20 @@ public abstract class RssHttpHandlerBaseAn event which is fired when the handler is initialized. public event InitEventHandler Init; + /// An event which is fired just before the handler's contents are rendered. public event PreRenderEventHandler PreRender; /// - bool IHttpHandler.IsReusable - { - get - { - return false; - } - } + bool IHttpHandler.IsReusable => false; - protected TRssChannelType Channel - { - get - { - return this.channel; - } - } + /// Gets the channel. + protected TRssChannelType Channel { get; private set; } - protected HttpContext Context - { - get - { - return this.context; - } - } + /// Gets the HTTP context. + protected HttpContext Context { get; private set; } /// void IHttpHandler.ProcessRequest(HttpContext context) @@ -61,9 +50,7 @@ void IHttpHandler.ProcessRequest(HttpContext context) this.OnInit(EventArgs.Empty); // parse the channel name and the user name from the query string - string userName; - string channelName; - RssHttpHandlerHelper.ParseChannelQueryString(context.Request, out channelName, out userName); + RssHttpHandlerHelper.ParseChannelQueryString(context.Request, out var channelName, out var userName); // populate items (call the derived class) this.PopulateChannel(channelName, userName); @@ -73,41 +60,42 @@ void IHttpHandler.ProcessRequest(HttpContext context) this.Render(new XmlTextWriter(this.Context.Response.OutputStream, null)); } - /// Triggers the Init event. + /// Triggers the event. + /// The event args. protected virtual void OnInit(EventArgs ea) { - if (this.Init != null) - { - this.Init(this, ea); - } + this.Init?.Invoke(this, ea); } - /// Triggers the PreRender event. + /// Triggers the event. + /// The event args. protected virtual void OnPreRender(EventArgs ea) { - if (this.PreRender != null) - { - this.PreRender(this, ea); - } + this.PreRender?.Invoke(this, ea); } + /// When overridden in a derived class, populates the channel name and user name. + /// The channel name. + /// The user name. protected virtual void PopulateChannel(string channelName, string userName) { } + /// Renders the channel as XML to the . + /// The write to which the channel should be rendered. protected virtual void Render(XmlTextWriter writer) { - XmlDocument doc = this.channel.SaveAsXml(); + XmlDocument doc = this.Channel.SaveAsXml(); doc.Save(writer); } private void InternalInit(HttpContext context) { - this.context = context; + this.Context = context; // create the channel - this.channel = new TRssChannelType(); - this.channel.SetDefaults(); + this.Channel = new TRssChannelType(); + this.Channel.SetDefaults(); this.Context.Response.ContentType = "text/xml"; } diff --git a/DNN Platform/Syndication/RSS/RssHttpHandlerHelper.cs b/DNN Platform/Syndication/RSS/RssHttpHandlerHelper.cs index ac8c5f946a1..f6ba33fed07 100644 --- a/DNN Platform/Syndication/RSS/RssHttpHandlerHelper.cs +++ b/DNN Platform/Syndication/RSS/RssHttpHandlerHelper.cs @@ -7,14 +7,18 @@ namespace DotNetNuke.Services.Syndication using System.Web; using System.Web.Security; - /// Helper class (for RssHtppHandler) to pack and unpack user name and channel to from/to query string. + /// Helper class (for ) to pack and unpack user name and channel to from/to query string. public class RssHttpHandlerHelper { private RssHttpHandlerHelper() { } - // helper to generate link [to the .ashx] containing channel name and (encoded) userName + /// Generate the link (to the .ashx) containing the channel name and (encoded) userName. + /// The handler path. + /// The channel name. + /// The user name. + /// A URL to the channel. public static string GenerateChannelLink(string handlerPath, string channelName, string userName) { string link = VirtualPathUtility.ToAbsolute(handlerPath); @@ -34,7 +38,7 @@ public static string GenerateChannelLink(string handlerPath, string channelName, } userName = "." + userName; // not to confuse the encrypted string with real auth ticket for real user - DateTime ticketDate = DateTime.Now.AddDays(-100); // already expried + DateTime ticketDate = DateTime.Now.AddDays(-100); // already expired var t = new FormsAuthenticationTicket(2, userName, ticketDate, ticketDate.AddDays(2), false, channelName, "/"); @@ -44,6 +48,10 @@ public static string GenerateChannelLink(string handlerPath, string channelName, return link; } + /// Parses the channel's query-string to extract the channel name and user name. + /// The HTTP request. + /// The channel name. + /// The user name or . internal static void ParseChannelQueryString(HttpRequest request, out string channelName, out string userName) { string ticket = request.QueryString["t"]; diff --git a/DNN Platform/Syndication/RSS/RssHyperLink.cs b/DNN Platform/Syndication/RSS/RssHyperLink.cs index a6fbd0160f4..62a22e39186 100644 --- a/DNN Platform/Syndication/RSS/RssHyperLink.cs +++ b/DNN Platform/Syndication/RSS/RssHyperLink.cs @@ -1,69 +1,45 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Services.Syndication -{ - using System; - using System.Web.UI; +namespace DotNetNuke.Services.Syndication +{ + using System; + using System.Web.UI; using System.Web.UI.WebControls; - /// RssHyperLink control - works with RssHttpHandler. - public class RssHyperLink : HyperLink - { - private string channelName; - private bool includeUserName; - + /// RssHyperLink control - works with . + public class RssHyperLink : HyperLink + { /// Initializes a new instance of the class. - public RssHyperLink() - { - this.Text = "RSS"; - } - - // passed to RssHttpHandler - public string ChannelName - { - get - { - return this.channelName; - } - - set - { - this.channelName = value; - } - } - - // when flag is set, the current user'd name is passed to RssHttpHandler - public bool IncludeUserName - { - get - { - return this.includeUserName; - } - - set - { - this.includeUserName = value; - } + public RssHyperLink() + { + this.Text = "RSS"; } + /// Gets or sets the channel name. + public string ChannelName { get; set; } + + /// Gets or sets a value indicating whether the current user's name is passed to RssHttpHandler. + public bool IncludeUserName { get; set; } + /// - protected override void OnPreRender(EventArgs e) - { - // modify the NavigateUrl to include optional user name and channel name - string channel = this.channelName != null ? this.channelName : string.Empty; - string user = this.includeUserName ? this.Context.User.Identity.Name : string.Empty; - this.NavigateUrl = RssHttpHandlerHelper.GenerateChannelLink(this.NavigateUrl, channel, user); - - // add to tag (if is present) - if (this.Page.Header != null) - { - string title = string.IsNullOrEmpty(channel) ? this.Text : channel; - - this.Page.Header.Controls.Add(new LiteralControl(string.Format("\r\n", title, this.NavigateUrl))); - } - - base.OnPreRender(e); - } - } -} + protected override void OnPreRender(EventArgs e) + { + // modify the NavigateUrl to include optional user name and channel name + string channel = this.ChannelName ?? string.Empty; + string user = this.IncludeUserName ? this.Context.User.Identity.Name : string.Empty; + this.NavigateUrl = RssHttpHandlerHelper.GenerateChannelLink(this.NavigateUrl, channel, user); + + // add to tag (if is present) + if (this.Page.Header != null) + { + string title = string.IsNullOrEmpty(channel) ? this.Text : channel; + + this.Page.Header.Controls.Add(new LiteralControl( + $"\r\n")); + } + + base.OnPreRender(e); + } + } +} diff --git a/DNN Platform/Syndication/RSS/RssXmlHelper.cs b/DNN Platform/Syndication/RSS/RssXmlHelper.cs index 26f801d2241..7afa318e9d5 100644 --- a/DNN Platform/Syndication/RSS/RssXmlHelper.cs +++ b/DNN Platform/Syndication/RSS/RssXmlHelper.cs @@ -8,10 +8,11 @@ namespace DotNetNuke.Services.Syndication using System.Web; using System.Xml; + /// A helper class for handling RSS XML. internal class RssXmlHelper { /// Internal helper class for XML to RSS conversion (and for generating XML from RSS). - /// + /// The XML document. /// A new instance. internal static RssChannelDom ParseChannelXml(XmlDocument doc) { @@ -91,6 +92,8 @@ internal static RssChannelDom ParseChannelXml(XmlDocument doc) return new RssChannelDom(channelAttributes, imageAttributes, itemsAttributesList); } + /// Creates an empty RSS XML document. + /// A new . internal static XmlDocument CreateEmptyRssXml() { var doc = new XmlDocument { XmlResolver = null }; @@ -100,6 +103,11 @@ internal static XmlDocument CreateEmptyRssXml() return doc; } + /// Copies the into the . + /// The node to which the new element will be added. + /// The element to copy. + /// The name of the new element to copy into. + /// The new XML element. internal static XmlNode SaveRssElementAsXml(XmlNode parentNode, RssElementBase element, string elementName) { XmlDocument doc = parentNode.OwnerDocument; diff --git a/DNN Platform/Syndication/Settings.cs b/DNN Platform/Syndication/Settings.cs index a58f90ec42b..bf8d9953da2 100644 --- a/DNN Platform/Syndication/Settings.cs +++ b/DNN Platform/Syndication/Settings.cs @@ -5,8 +5,10 @@ namespace DotNetNuke.Services.Syndication { using System.Diagnostics.CodeAnalysis; + /// Holder of settings values. internal static class Settings { + /// The path to the root of the cache folder. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Breaking change")] internal static string CacheRoot = "Portals/_default/Cache"; }