screwturn-4/WebApplication/RSS.aspx.cs
2009-09-30 13:47:13 +00:00

610 lines
21 KiB
C#

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using ScrewTurn.Wiki.PluginFramework;
using System.Text;
using System.Xml;
namespace ScrewTurn.Wiki {
public partial class RSS : BasePage {
private string currentNamespace = null;
private RssFeedsMode rssFeedsMode;
protected void Page_Load(object sender, EventArgs e) {
rssFeedsMode = Settings.RssFeedsMode;
if(rssFeedsMode == RssFeedsMode.Disabled) {
Response.Clear();
Response.StatusCode = 404;
Response.End();
return;
}
string currentUsername = SessionFacade.GetCurrentUsername();
string[] currentGroups = SessionFacade.GetCurrentGroupNames();
currentNamespace = DetectNamespace();
if(string.IsNullOrEmpty(currentNamespace)) currentNamespace = null;
if(SessionFacade.LoginKey == null) {
// Look for username/password in the query string
if(Request["Username"] != null && Request["Password"] != null) {
// Try to authenticate
UserInfo u = Users.FindUser(Request["Username"]);
if(u != null) {
// Very "dirty" way - pages should not access Providers
if(u.Provider.TestAccount(u, Request["Password"])) {
// Valid account
currentUsername = Request["Username"];
currentGroups = Users.FindUser(currentUsername).Groups;
}
}
else {
// Check for built-in admin account
if(Request["Username"].Equals("admin") && Request["Password"].Equals(Settings.MasterPassword)) {
currentUsername = "admin";
currentGroups = new string[] { Settings.AdministratorsGroup };
}
}
}
}
Response.ClearContent();
Response.ContentType = "text/xml;charset=UTF-8";
Response.ContentEncoding = System.Text.UTF8Encoding.UTF8;
if(Request["Page"] != null) {
PageInfo page = Pages.FindPage(Request["Page"]);
if(page == null) return;
PageContent content = Content.GetPageContent(page, true);
if(Request["Discuss"] == null) {
// Check permission for the page
bool canReadPage = AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadPage,
currentUsername, currentGroups);
if(!canReadPage) {
Response.StatusCode = 401;
return;
}
// Start an XML writer for the output stream
using (XmlWriter rss = XmlWriter.Create(Response.OutputStream))
{
// Build an RSS header
BuildRssHeader(rss);
// Build the channel element
BuildChannelHead(rss, Settings.WikiTitle + " - " + Formatter.StripHtml(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, page)),
Settings.MainUrl + page.FullName + Settings.PageExtension,
Settings.MainUrl + UrlTools.BuildUrl("RSS.aspx?Page=", page.FullName),
Formatter.StripHtml(content.Title) + " - " + Properties.Messages.PageUpdates);
// Write the item element
rss.WriteStartElement("item");
rss.WriteStartElement("title");
rss.WriteCData(Formatter.StripHtml(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, page)));
rss.WriteEndElement();
rss.WriteElementString("link", Settings.MainUrl + page.FullName + Settings.PageExtension);
// Create the description tag
rss.WriteStartElement("description");
if (rssFeedsMode == RssFeedsMode.Summary)
{
rss.WriteCData(Formatter.StripHtml(content.Title) + ": " + Properties.Messages.ThePageHasBeenUpdatedBy + " " +
content.User + (content.Comment.Length > 0 ? ".<br />" + content.Comment : "."));
}
else
{
rss.WriteCData(Content.GetFormattedPageContent(page, false));
}
rss.WriteEndElement();
// Write the remaining elements
BuildAuthorTag(rss, content.User);
rss.WriteElementString("pubDate", content.LastModified.ToUniversalTime().ToString("R"));
rss.WriteStartElement("guid");
rss.WriteAttributeString("isPermaLink", "false");
rss.WriteString(GetGuid(page.FullName, content.LastModified));
rss.WriteEndElement();
// Complete the item element
CompleteCurrentElement(rss);
// Complete the channel element
CompleteCurrentElement(rss);
// Complete the rss element
CompleteCurrentElement(rss);
// Finish off
rss.Flush();
rss.Close();
}
}
else {
// Check permission for the discussion
bool canReadDiscussion = AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadDiscussion,
currentUsername, currentGroups);
if(!canReadDiscussion) {
Response.StatusCode = 401;
return;
}
List<Message> messages = new List<Message>(Pages.GetPageMessages(page));
// Un-tree Messages
messages = UnTreeMessages(messages);
// Sort from newer to older
messages.Sort(new MessageDateTimeComparer(true));
// Start an XML writer for the output stream
using (XmlWriter rss = XmlWriter.Create(Response.OutputStream))
{
// Build an RSS header
BuildRssHeader(rss);
// Build the channel element
BuildChannelHead(rss, Settings.WikiTitle + " - " + Formatter.StripHtml(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, page)) + " - Discussion Updates",
Settings.MainUrl + page.FullName + Settings.PageExtension + "?Discuss=1",
Settings.MainUrl + UrlTools.BuildUrl("RSS.aspx?Page=", page.FullName, "&Discuss=1"),
Settings.WikiTitle + " - " + Formatter.StripHtml(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, page)) + " - Discussion Updates");
for (int i = 0; i < messages.Count; i++)
{
// Write the item element
rss.WriteStartElement("item");
rss.WriteStartElement("title");
rss.WriteCData(Formatter.StripHtml(FormattingPipeline.PrepareTitle(messages[i].Subject, false, FormattingContext.MessageBody, page)));
rss.WriteEndElement();
rss.WriteElementString("link", Settings.MainUrl + page.FullName + Settings.PageExtension + "?Discuss=1");
// Create the description tag
rss.WriteStartElement("description");
if (rssFeedsMode == RssFeedsMode.Summary)
{
rss.WriteCData(Properties.Messages.AMessageHasBeenPostedBy.Replace("##SUBJECT##", messages[i].Subject) + " " + messages[i].Username + ".");
}
else
{
rss.WriteCData(FormattingPipeline.FormatWithPhase3(FormattingPipeline.FormatWithPhase1And2(messages[i].Body, false, FormattingContext.MessageBody, page), FormattingContext.MessageBody, page));
}
rss.WriteEndElement();
// Write the remaining elements
BuildAuthorTag(rss, messages[i].Username);
rss.WriteElementString("pubDate", content.LastModified.ToUniversalTime().ToString("R"));
rss.WriteStartElement("guid");
rss.WriteAttributeString("isPermaLink", "false");
rss.WriteString(GetGuid(page.FullName + "-" + messages[i].ID.ToString(), messages[i].DateTime));
rss.WriteEndElement();
// Complete the item element
CompleteCurrentElement(rss);
}
// Complete the channel element
CompleteCurrentElement(rss);
// Complete the rss element
CompleteCurrentElement(rss);
// Finish off
rss.Flush();
rss.Close();
}
}
}
else {
if(Request["Discuss"] == null) {
// All page updates
// Start an XML writer for the output stream
using(XmlWriter rss = XmlWriter.Create(Response.OutputStream)) {
// Build an RSS header
BuildRssHeader(rss);
bool useCat = false;
string cat = "";
if(Request["Category"] != null) {
useCat = true;
cat = Request["Category"];
}
// Build the channel element
BuildChannelHead(rss, Settings.WikiTitle + " - " + Properties.Messages.PageUpdates,
Settings.MainUrl,
Settings.MainUrl + UrlTools.BuildUrl("RSS.aspx", (useCat ? ("?Category=" + cat) : "")),
Properties.Messages.RecentPageUpdates);
RecentChange[] ch = RecentChanges.GetAllChanges();
Array.Reverse(ch);
for(int i = 0; i < ch.Length; i++) {
// Suppress this entry if we've already reported this page (so we don't create duplicate entries in the feed page)
bool duplicateFound = false;
for(int j = 0; j < i; j++) {
if (ch[j].Page == ch[i].Page) {
duplicateFound = true;
break;
}
}
if(duplicateFound) continue;
// Skip message-related entries
if(!IsPageChange(ch[i].Change)) continue;
PageInfo p = Pages.FindPage(ch[i].Page);
if(p != null) {
// Check permissions for every page
bool canReadThisPage = AuthChecker.CheckActionForPage(p, Actions.ForPages.ReadPage,
currentUsername, currentGroups);
if(!canReadThisPage) continue;
if(useCat) {
CategoryInfo[] infos = Pages.GetCategoriesForPage(p);
if(infos.Length == 0 && cat != "-") continue;
else if(infos.Length != 0) {
bool found = false;
for(int k = 0; k < infos.Length; k++) {
if(infos[k].FullName == cat) {
found = true;
break;
}
}
if(!found) continue;
}
}
}
// Check namespace
if(p != null && NameTools.GetNamespace(p.FullName) != currentNamespace) continue;
// Write the item element
rss.WriteStartElement("item");
rss.WriteStartElement("title");
rss.WriteCData(Formatter.StripHtml(FormattingPipeline.PrepareTitle(ch[i].Title, false, FormattingContext.PageContent, p)));
rss.WriteEndElement();
if (ch[i].Change != Change.PageDeleted && p != null)
rss.WriteElementString("link", Settings.MainUrl + ch[i].Page + Settings.PageExtension);
else
rss.WriteElementString("link", Settings.MainUrl);
BuildAuthorTag(rss, ch[i].User);
// Create the description tag
StringBuilder sb = new StringBuilder();
if(rssFeedsMode == RssFeedsMode.Summary || p == null) {
switch(ch[i].Change) {
case Change.PageUpdated:
sb.Append(Properties.Messages.ThePageHasBeenUpdatedBy);
break;
case Change.PageDeleted:
sb.Append(Properties.Messages.ThePageHasBeenDeletedBy);
break;
case Change.PageRenamed:
sb.Append(Properties.Messages.ThePageHasBeenRenamedBy);
break;
case Change.PageRolledBack:
sb.Append(Properties.Messages.ThePageHasBeenRolledBackBy);
break;
}
sb.Append(" " + ch[i].User + (ch[i].Description.Length > 0 ? ".<br />" + ch[i].Description : "."));
}
else {
// p != null
sb.Append(Content.GetFormattedPageContent(p, false));
}
rss.WriteStartElement("description");
rss.WriteCData(sb.ToString());
rss.WriteEndElement();
// Write the remaining elements
rss.WriteElementString("pubDate", ch[i].DateTime.ToUniversalTime().ToString("R"));
rss.WriteStartElement("guid");
rss.WriteAttributeString("isPermaLink", "false");
rss.WriteString(GetGuid(ch[i].Page, ch[i].DateTime));
rss.WriteEndElement();
// Complete the item element
rss.WriteEndElement();
}
// Complete the channel element
CompleteCurrentElement(rss);
// Complete the rss element
CompleteCurrentElement(rss);
// Finish off
rss.Flush();
rss.Close();
}
}
else {
// All discussion updates
// Start an XML writer for the output stream
using (XmlWriter rss = XmlWriter.Create(Response.OutputStream))
{
// Build an RSS header
BuildRssHeader(rss);
bool useCat = false;
string cat = "";
if (Request["Category"] != null)
{
useCat = true;
cat = Request["Category"];
}
// Build the channel element
BuildChannelHead(rss, Settings.WikiTitle + " - " + Properties.Messages.DiscussionUpdates,
Settings.MainUrl,
Settings.MainUrl + UrlTools.BuildUrl("RSS.aspx", (useCat ? ("?Category=" + cat) : "")),
Properties.Messages.RecentDiscussionUpdates);
RecentChange[] ch = RecentChanges.GetAllChanges();
Array.Reverse(ch);
for (int i = 0; i < ch.Length; i++)
{
// Skip page-related entries
if (!IsMessageChange(ch[i].Change)) continue;
PageInfo p = Pages.FindPage(ch[i].Page);
if (p != null)
{
// Check permissions for every page
bool canReadThisPageDiscussion = AuthChecker.CheckActionForPage(p, Actions.ForPages.ReadDiscussion,
currentUsername, currentGroups);
if (!canReadThisPageDiscussion) continue;
if (useCat)
{
CategoryInfo[] infos = Pages.GetCategoriesForPage(p);
if (infos.Length == 0 && cat != "-") continue;
else if (infos.Length != 0)
{
bool found = false;
for (int k = 0; k < infos.Length; k++)
{
if (infos[k].FullName == cat)
{
found = true;
break;
}
}
if (!found) continue;
}
}
// Check namespace
if (NameTools.GetNamespace(p.FullName) != currentNamespace) continue;
// Write the item element
rss.WriteStartElement("item");
rss.WriteStartElement("title");
rss.WriteCData(Properties.Messages.Discussion + ": " + Formatter.StripHtml(FormattingPipeline.PrepareTitle(ch[i].Title, false, FormattingContext.PageContent, p)));
rss.WriteEndElement();
string id = Tools.GetMessageIdForAnchor(ch[i].DateTime);
if (ch[i].Change != Change.MessageDeleted)
{
rss.WriteElementString("link", Settings.MainUrl + ch[i].Page + Settings.PageExtension + "?Discuss=1#" + id);
}
else rss.WriteElementString("link", Settings.MainUrl + ch[i].Page + Settings.PageExtension + "?Discuss=1");
string messageContent = FindMessageContent(ch[i].Page, id);
// Create the description tag
StringBuilder sb = new StringBuilder();
if (rssFeedsMode == RssFeedsMode.Summary || messageContent == null)
{
switch (ch[i].Change)
{
case Change.MessagePosted:
sb.Append(Properties.Messages.AMessageHasBeenPostedBy.Replace("##SUBJECT##", ch[i].MessageSubject));
break;
case Change.MessageEdited:
sb.Append(Properties.Messages.AMessageHasBeenEditedBy.Replace("##SUBJECT##", ch[i].MessageSubject));
break;
case Change.MessageDeleted:
sb.Append(Properties.Messages.AMessageHasBeenDeletedBy.Replace("##SUBJECT##", ch[i].MessageSubject));
break;
}
sb.Append(" " + ch[i].User + (ch[i].Description.Length > 0 ? ".<br />" + ch[i].Description : "."));
}
else
{
sb.Append(FormattingPipeline.FormatWithPhase3(FormattingPipeline.FormatWithPhase1And2(messageContent, false, FormattingContext.MessageBody, null), FormattingContext.MessageBody, null));
}
rss.WriteStartElement("description");
rss.WriteCData(sb.ToString());
rss.WriteEndElement();
// Write the remaining elements
BuildAuthorTag(rss, ch[i].User);
rss.WriteElementString("pubDate", ch[i].DateTime.ToUniversalTime().ToString("R"));
rss.WriteStartElement("guid");
rss.WriteAttributeString("isPermaLink", "false");
rss.WriteString(GetGuid(ch[i].Page, ch[i].DateTime));
rss.WriteEndElement();
// Complete the item element
rss.WriteEndElement();
}
}
// Complete the channel element
CompleteCurrentElement(rss);
// Complete the rss element
CompleteCurrentElement(rss);
// Finish off
rss.Flush();
rss.Close();
}
}
}
}
/// <summary>
/// Tries to find the content of a message.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="messageId">The ID of the message, built using Tools.GetMessageIdForAnchor(...).</param>
/// <returns>The message content, or <c>null</c>.</returns>
private string FindMessageContent(string pageName, string messageId) {
PageInfo page = Pages.FindPage(pageName);
if(page == null) return null;
Message[] messages = Pages.GetPageMessages(page);
if(messages.Length == 0) return null;
List<Message> linearMessages = UnTreeMessages(messages);
foreach(Message msg in linearMessages) {
if(messageId == Tools.GetMessageIdForAnchor(msg.DateTime)) return msg.Body;
}
return null;
}
/// <summary>
/// Determines whether a change refers to page content.
/// </summary>
/// <param name="change">The change.</param>
/// <returns><c>true</c> if the change refers to page content.</returns>
private static bool IsPageChange(Change change) {
return change == Change.PageUpdated || change == Change.PageRolledBack ||
change == Change.PageRenamed || change == Change.PageDeleted;
}
/// <summary>
/// Determines whether a change refers to a message.
/// </summary>
/// <param name="change">The change.</param>
/// <returns><c>true</c> if the change refers to a message.</returns>
private static bool IsMessageChange(Change change) {
return change == Change.MessagePosted || change == Change.MessageEdited || change == Change.MessageDeleted;
}
/// <summary>
/// Deconstructs a tree of messages and converts it into a flat list.
/// </summary>
/// <param name="messages">The input tree.</param>
/// <returns>The resulting flat list.</returns>
private static List<Message> UnTreeMessages(IEnumerable<Message> messages) {
List<Message> output = new List<Message>(20);
output.AddRange(messages);
foreach(Message msg in messages) {
output.AddRange(UnTreeMessages(msg.Replies));
}
return output;
}
/// <summary>
/// Gets a valid and consistent GUID for RSS items.
/// </summary>
/// <param name="item">The item name, for example the page name.</param>
/// <param name="editDateTime">The last date/time the item was modified.</param>
/// <returns>The GUID.</returns>
private string GetGuid(string item, DateTime editDateTime) {
return Hash.Compute(item + editDateTime.ToString("yyyyMMddHHmmss"));
}
// Atom namespace constants
private const string atomPrefix = "atom";
private const string atomNs = "http://www.w3.org/2005/Atom";
/// <summary>
/// Sends the RSS header to the output stream.
/// </summary>
/// <param name="rss">The output stream.</param>
private void BuildRssHeader(XmlWriter rss) {
// Create a version 2.0 heading
rss.WriteStartElement("rss");
rss.WriteAttributeString("version", "2.0");
// Define the atom namespace so we can include a self-referencing link
rss.WriteAttributeString("xmlns", atomPrefix, null, atomNs);
}
/// <summary>
/// Sends the channel head to the output stream.
/// </summary>
/// <param name="rss">The output stream.</param>
/// <param name="title">The title.</param>
/// <param name="link">The link.</param>
/// <param name="selfLink">The self link (atom).</param>
/// <param name="description">The description.</param>
/// <returns>The complete channel head.</returns>
private void BuildChannelHead(XmlWriter rss, string title, string link, string selfLink, string description)
{
rss.WriteStartElement("channel");
rss.WriteStartElement("title");
rss.WriteCData(title);
rss.WriteEndElement();
rss.WriteElementString("link", link);
rss.WriteStartElement(atomPrefix, "link", atomNs);
rss.WriteAttributeString("href", selfLink);
rss.WriteAttributeString("rel", "self");
rss.WriteAttributeString("type", "application/rss+xml");
rss.WriteEndElement();
rss.WriteStartElement("description");
rss.WriteCData(description);
rss.WriteEndElement();
rss.WriteElementString("pubDate", DateTime.Now.ToString("R"));
rss.WriteElementString("generator", "ScrewTurn Wiki RSS Feed Generator");
}
/// <summary>
/// Completes an element in the output stream.
/// </summary>
/// <param name="rss">The output stream.</param>
private void CompleteCurrentElement(XmlWriter rss) {
rss.WriteEndElement();
}
/// <summary>
/// Sends an author tag to the output stream.
/// </summary>
/// <param name="rss">The output stream.</param>
/// <param name="userName">The author's user name.</param>
private void BuildAuthorTag(XmlWriter rss, string userName) {
UserInfo author = Users.FindUser(userName);
if(author == null) {
rss.WriteElementString("author", userName);
}
else {
rss.WriteElementString("author", string.Format("{0} ({1})", Formatter.StripHtml(author.DisplayName), userName));
}
}
}
}