Files
ayanova7/source/bizobjects/AyaLib/GZTW.AyaNova.BLL/WikiPage.cs
2018-06-29 19:47:36 +00:00

1749 lines
63 KiB
C#

///////////////////////////////////////////////////////////
// WikiPage.cs
// Implementation of Class WikiPage
// CSLA type: Editable Root
// Created on: 29-Rocktober-2008
// Object design: John
// Coded: 29-Rocktober-2008
///////////////////////////////////////////////////////////
using System;
using System.Data;
using CSLA.Data;
using GZTW.Data;
using CSLA;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
//case 73
namespace GZTW.AyaNova.BLL
{
/// <summary>
/// WikiPage object
///
/// </summary>
[Serializable]
public class WikiPage : BusinessBase
{
#region Attributes
private Guid mID;
private SmartDate mCreated;
private SmartDate mModified;
private bool mInternalOnly;
private Guid mCreator;
private Guid mModifier;
private Guid mRootObjectID = Guid.Empty;
private RootObjectTypes mRootObjectType;
private string mTitle = null;
private byte[] mContent = null;
private bool bReadOnly = false;
//case 1584
private RootObjectTypes mExactObjectType;
#endregion
#region Constructor
/// <summary>
/// Private constructor to prevent direct instantiation
/// </summary>
private WikiPage()
{
//New ID
mID = Guid.NewGuid();
InternalOnly = true;
//Set record history to defaults
mCreated = new SmartDate(DBUtil.CurrentWorkingDateTime);
mModified = new SmartDate();
mCreator = Guid.Empty;
mModifier = Guid.Empty;
//pre-break various rules
Title = "";
RootObjectID = Guid.Empty;
RootObjectType = RootObjectTypes.Nothing;
mExactObjectType = RootObjectTypes.Nothing;
mContent = new byte[0];
}
#endregion
#region Business properties
/// <summary>
/// Internal Unique GUID value of WikiPage record in database
/// </summary>
public Guid ID
{
get
{
return mID;
}
}
/// <summary>
/// Get created date
///
///
/// </summary>
public string Created
{
get
{
return mCreated.ToString();
}
}
/// <summary>
/// Get modified date
///
///
/// </summary>
public string Modified
{
get
{
return mModified.ToString();
}
}
/// <summary>
/// Get user record ID of person who created this record
///
///
/// </summary>
public Guid Creator
{
get
{
return mCreator;
}
}
/// <summary>
/// Get user ID of person who modified this record
///
///
/// </summary>
public Guid Modifier
{
get
{
return mModifier;
}
}
/// <summary>
/// AyaNova object ID
/// </summary>
public Guid RootObjectID
{
get
{
return mRootObjectID;
}
set
{
if (bReadOnly)
ThrowSetError();
else
{
if (mRootObjectID != value)
{
mRootObjectID = value;
BrokenRules.Assert("RootObjectID", "Error.Object.RequiredFieldEmpty,WikiPage.Label.RootObjectID", "RootObjectID", value == Guid.Empty);
MarkDirty();
}
}
}
}
/// <summary>
/// AyaNova object type
/// </summary>
public RootObjectTypes RootObjectType
{
get
{
return mRootObjectType;
}
set
{
if (bReadOnly)
ThrowSetError();
else
{
if (mRootObjectType != value)
{
mRootObjectType = value;
BrokenRules.Assert("RootObjectType", "Error.Object.RequiredFieldEmpty,WikiPage.Label.RootObjectType", "RootObjectType", value == RootObjectTypes.Nothing);
MarkDirty();
}
}
}
}
/// <summary>
/// Wikipages associated with any workorder type are saved attributed to rootobject type Workorder.
/// Since version 7 that has been deprecated but to maintain backward compatibility wikipages are still
/// associated with Workorder.
///
/// This properly shows the actual exact workorder type that retrieved this wikipage object.
/// </summary>
public RootObjectTypes ExactRootObjectType
{
get
{
return mExactObjectType;
}
}
/// <summary>
///If true (default) then only visible to AyaNova usertypes who are not
///a client.
/// </summary>
public bool InternalOnly
{
get
{
return mInternalOnly;
}
set
{
if (bReadOnly)
ThrowSetError();
else
{
if (mInternalOnly != value)
{
mInternalOnly = value;
MarkDirty();
}
}
}
}
/// <summary>
/// Title displayed on wiki page if set 1-255 Unicode characters
/// </summary>
public string Title
{
get
{
return mTitle;
}
set
{
if (bReadOnly)
ThrowSetError();
else
{
if (mTitle != value)
{
mTitle = value;
//BrokenRules.Assert("NameRequired",
// "Error.Object.RequiredFieldEmpty,WikiPage.Label.Title",
// "Title", value.Length == 0);
BrokenRules.Assert("NameLength",
"Error.Object.FieldLengthExceeded255,WikiPage.Label.Title",
"Title", value.Length > 255);
MarkDirty();
}
}
}
}
/// <summary>
/// Internal flag set from AyaNova / WBI when content is dirty
/// but WikiPage hasn't had it's content updated yet
/// </summary>
public bool HasDirtyContent
{
set
{
if (value == true)
MarkDirty();
}
}
/// <summary>
/// Indicates whether page has content or not
/// </summary>
public bool HasContent
{
get
{
return mContent.GetLength(0) > 0;
}
}
/// <summary>
/// Get the content of the wiki page which is stored in HTML
/// format natively as plain text
/// </summary>
public string GetContentAsPlainText
{
get
{
string s = GetContentAsString;
s = s.Replace("<br/>", "\r\n");
s = s.Replace("<br>", "\r\n");
s = System.Text.RegularExpressions.Regex.Replace(s, "<[^>]+?>", "");
s = AyaBizUtils.rxAyaImageTags.Replace(s, "");
s = AyaBizUtils.rxAyaLinks.Replace(s, "");
s = s.Replace("&nbsp;", " ");
s = s.Replace("&gt;", ">");
s = s.Replace("&lt;", "<");
return s;
}
}
/// <summary>
/// Get's content with WBI ready AyaImage: tags and AyaNova: urls
/// </summary>
/// <param name="bForEditing">true=don't build file list</param>
/// <returns></returns>
public string GetContentAsWBIReadyHTML(bool bForEditing)
{
string s = GetContentAsString;
MatchCollection mc = null;
List<string> list = new List<string>();
#region urlify any urls that aren't already anchored
if (!bForEditing)
{
//remove all anchored links first
MatchCollection mcHrefs = AyaBizUtils.rxAllHrefTags.Matches(s);
foreach (Match m in mcHrefs)
s = s.Replace(m.Value, "[PH:" + m.Index + "]");
//now find all the anchorable links
List<string> listProtocoled = new List<string>();
List<string> listEmail = new List<string>();
List<string> listWeb = new List<string>();
//This is for when a user in wbi is viewing a read only
//wiki page to automatically display all discernable urls
//with anchor tags so the user can click on them to activate
list.Clear();
//Hack to avoid double processing
//without this urls with protocols of mail or http
//pre-existing (and more than once) will get double replaced
s = s.Replace("http://", "");
s = s.Replace("mailto://", "");
mc = AyaBizUtils.rxAllUrlsWithProtocol.Matches(s);
foreach (Match m in mc)
if (!listProtocoled.Contains(m.Value))
listProtocoled.Add(m.Value);
mc = AyaBizUtils.rxEmailWithoutProtocol.Matches(s);
foreach (Match m in mc)
if (!listEmail.Contains(m.Groups["url"].Value))
listEmail.Add(m.Groups["url"].Value);
mc = AyaBizUtils.rxWebsiteUrlsWithoutProtocol.Matches(s);
foreach (Match m in mc)
if (!listWeb.Contains(m.Groups["url"].Value))
listWeb.Add(m.Groups["url"].Value);
//iterate list and replace matches with anchor tag
foreach (string surl in listProtocoled)
s = s.Replace(surl, "<a href=\"" + surl + "\" target=\"_blank\">" + surl + "</a>");
foreach (string surl in listEmail)
s = s.Replace(surl, "<a href=\"" + "mailto://" + surl + "\" target=\"_blank\">" + surl + "</a>");
foreach (string surl in listWeb)
s = s.Replace(surl, "<a href=\"" + "http://" + surl + "\" target=\"_blank\">" + surl + "</a>");
//ok, at this point we should have proper urls in s and original ones still X'd out
//so replace the x's with the original url
foreach (Match m in mcHrefs)
{
//need to insert a target tag for the url if none found
string sTarget = m.Value;
if (!AyaBizUtils.rsHrefContainsTargetTag.IsMatch(sTarget))
{
sTarget = sTarget.Replace("\">", "\" target=\"_blank\">");
}
s = s.Replace("[PH:" + m.Index + "]", sTarget);
}
}
#endregion
#region Images
mc = AyaBizUtils.rxAyaImageTags.Matches(s);
foreach (Match m in mc)
{
s = s.Replace(m.Value, "<img src=\"AyaImage.ashx?id=" + m.Groups["guid"].Value + "\">");
}
#endregion images
#region AyaNova: links
if (!bForEditing)
{
mc = AyaBizUtils.rxAyaLinks.Matches(s);
foreach (Match m in mc)
{
TypeAndID tid = TypeAndID.ParseAyaURL(m.Value);
if (tid != null)
{
string toUrl = "";
switch (tid.RootObjectType)
{
case RootObjectTypes.Part:
toUrl = "PartInventoryView.aspx?id=" + tid.ID.ToString();
break;
case RootObjectTypes.WikiPage:
toUrl = "Wiki?id=" + tid.ID.ToString();
break;
default:
{
if (tid.ID == Guid.Empty)
toUrl = tid.RootObjectType.ToString() + "Edit.aspx";
else
toUrl = tid.RootObjectType.ToString() + "Edit.aspx?id=" + tid.ID.ToString();
}
break;
}
string name = NameFetcher.GetItem(tid).RecordName;
s = s.Replace(m.Value, "<a href=\"" + toUrl + "\"target=\"_blank\">" + name + "</a>");
}
}
}
#endregion ayanova: links
#region wo: style links
if (!bForEditing)
{
mc = AyaBizUtils.rxAyaWOLinks.Matches(s);
foreach (Match m in mc)
{
Guid g = Guid.Empty;
WorkorderTypes wotype = WorkorderTypes.Service;
string sType = m.Groups[1].Value.ToLowerInvariant();
switch (sType)
{
case "quote:":
wotype = WorkorderTypes.Quote;
break;
case "pm:":
wotype = WorkorderTypes.PreventiveMaintenance;
break;
}
g = WorkorderInternalIDFetcher.GetItem(m.Groups[2].Value, wotype);
string toUrl = "WorkorderEdit.aspx?id=" + g.ToString();
if (g != Guid.Empty && (AyaBizUtils.InYourRegion(ObjectRegionIDFetcher.ObjectRegion(new TypeAndID(RootObjectTypes.Workorder, g)))))//case 58
s = s.Replace(m.Value, "<a href=\"" + toUrl + "\"target=\"_blank\">" + m.Value + "</a>");
}
}
#endregion
return s;
}
/// <summary>
/// Get's content with AyaNova RI ready AyaImage: tags and AyaNova: urls
/// </summary>
/// <param name="bForEditing">true=don't build file list</param>
/// <param name="baseSiteURL"></param>
/// <returns></returns>
public string GetContentAsRIReadyHTML(bool bForEditing, string baseSiteURL)
{
string s = GetContentAsString;
MatchCollection mc = null;
List<string> list = new List<string>();
#region urlify any urls that aren't already anchored
if (!bForEditing)
{
//remove all anchored links first
MatchCollection mcHrefs = AyaBizUtils.rxAllHrefTags.Matches(s);
foreach (Match m in mcHrefs)
s = s.Replace(m.Value, "[PH:" + m.Index + "]");
//now find all the anchorable links
List<string> listProtocoled = new List<string>();
List<string> listEmail = new List<string>();
List<string> listWeb = new List<string>();
//This is for when a user in wbi is viewing a read only
//wiki page to automatically display all discernable urls
//with anchor tags so the user can click on them to activate
list.Clear();
//Hack to avoid double processing
//without this urls with protocols of mail or http
//pre-existing (and more than once) will get double replaced
s = s.Replace("http://", "");
s = s.Replace("mailto://", "");
mc = AyaBizUtils.rxAllUrlsWithProtocol.Matches(s);
foreach (Match m in mc)
if (!listProtocoled.Contains(m.Value))
listProtocoled.Add(m.Value);
mc = AyaBizUtils.rxEmailWithoutProtocol.Matches(s);
foreach (Match m in mc)
if (!listEmail.Contains(m.Groups["url"].Value))
listEmail.Add(m.Groups["url"].Value);
mc = AyaBizUtils.rxWebsiteUrlsWithoutProtocol.Matches(s);
foreach (Match m in mc)
if (!listWeb.Contains(m.Groups["url"].Value))
listWeb.Add(m.Groups["url"].Value);
//iterate list and replace matches with anchor tag
foreach (string surl in listProtocoled)
s = s.Replace(surl, "<a href=\"" + surl + "\" target=\"_blank\">" + surl + "</a>");
foreach (string surl in listEmail)
s = s.Replace(surl, "<a href=\"" + "mailto://" + surl + "\" target=\"_blank\">" + surl + "</a>");
foreach (string surl in listWeb)
s = s.Replace(surl, "<a href=\"" + "http://" + surl + "\" target=\"_blank\">" + surl + "</a>");
//ok, at this point we should have proper urls in s and original ones still X'd out
//so replace the x's with the original url
foreach (Match m in mcHrefs)
{
//need to insert a target tag for the url if none found
string sTarget = m.Value;
if (!AyaBizUtils.rsHrefContainsTargetTag.IsMatch(sTarget))
{
sTarget = sTarget.Replace("\">", "\" target=\"_blank\">");
}
s = s.Replace("[PH:" + m.Index + "]", sTarget);
}
}
#endregion
#region Images
mc = AyaBizUtils.rxAyaImageTags.Matches(s);
foreach (Match m in mc)
{
s = s.Replace(m.Value, "<img src=\"" + baseSiteURL + "ayImage/view/" + m.Groups["guid"].Value + "\">");
}
#endregion images
#region AyaNova: links
if (!bForEditing)
{
mc = AyaBizUtils.rxAyaLinks.Matches(s);
foreach (Match m in mc)
{
TypeAndID tid = TypeAndID.ParseAyaURL(m.Value);
if (tid != null)
{
string toUrl = "";
switch (tid.RootObjectType)
{
case RootObjectTypes.Nothing:
toUrl = baseSiteURL;
break;
//In RI all editable objects have a path that is the root object type name plus /edit/{guidvalue}
//so this should work (in theory)
default:
{
if (tid.ID == Guid.Empty)
toUrl = tid.RootObjectType.ToString() + "/edit/";
else
toUrl = tid.RootObjectType.ToString() + "/edit/" + tid.ID.ToString();
}
break;
}
string name = NameFetcher.GetItem(tid).RecordName;
s = s.Replace(m.Value, "<a href=\"" + toUrl + "\"target=\"_blank\">" + name + "</a>");
}
}
}
#endregion ayanova: links
#region wo: style links
if (!bForEditing)
{
mc = AyaBizUtils.rxAyaWOLinks.Matches(s);
foreach (Match m in mc)
{
Guid g = Guid.Empty;
WorkorderTypes wotype = WorkorderTypes.Service;
string sType = m.Groups[1].Value.ToLowerInvariant();
switch (sType)
{
case "quote:":
wotype = WorkorderTypes.Quote;
break;
case "pm:":
wotype = WorkorderTypes.PreventiveMaintenance;
break;
}
g = WorkorderInternalIDFetcher.GetItem(m.Groups[2].Value, wotype);
//*** WRONG: this fuckery is because RI has separate edit pages for different workorder types whereas older UI's didn't ***: wrong
//case 2026 - above is wrong, actually it's just workorder/edit and the id now
//changed block below to reflect this
string toUrl = string.Empty;
switch (wotype)
{
case WorkorderTypes.Service:
toUrl = baseSiteURL + "Workorder/edit/" + g.ToString();
break;
case WorkorderTypes.Quote:
toUrl = baseSiteURL + "Workorder/edit/" + g.ToString();
break;
case WorkorderTypes.PreventiveMaintenance:
toUrl = baseSiteURL + "Workorder/edit/" + g.ToString();
break;
}
//Note wbi code checks if in region here, I think we'll let RI handle that
s = s.Replace(m.Value, "<a href=\"" + toUrl + "\"target=\"_blank\">" + m.Value + "</a>");
}
}
#endregion
RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Singleline;
Regex rxBody = new Regex("<body>(?<theBody>.*)</body>", options);
Match matchBody = rxBody.Match(s);
string theBody = string.Empty;
Regex rxStyle = new Regex("<style(?<theStyle>.*)</style>", options);
Match matchStyle = rxStyle.Match(s);
string theStyle = string.Empty;
if (matchStyle.Success)
{
theStyle = matchStyle.Groups["theStyle"].Value;
}
if (matchBody.Success)
{
theBody = matchBody.Groups["theBody"].Value;
}
//case 1975
//Handle empty body
if (string.IsNullOrWhiteSpace(theBody) || !string.IsNullOrWhiteSpace(s))
theBody = s;
if (bForEditing)
return theBody;//don't return the style, needs to be fetched separately then fed to the editor at the client end separately
else
{
if (string.IsNullOrWhiteSpace(theStyle))
return theBody;
else
return "<style " + theStyle + "</style>\r\n" + theBody;
}
}
/// <summary>
/// Get the style
/// </summary>
/// <returns></returns>
public string GetStyleContentOnly()
{
string s = GetContentAsString;
RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Singleline;
Regex rxStyle = new Regex("<style(?<theStyle>.*)</style>", options);
Match matchStyle = rxStyle.Match(s);
string theStyle = string.Empty;
if (matchStyle.Success)
{
theStyle = matchStyle.Groups["theStyle"].Value;
return "<style " + theStyle + "</style>";
}
return "<style></style>";
}
/// <summary>
/// Fetches HTML fragment for file list area of web page in WBI based on files in current wiki page
/// </summary>
/// <returns></returns>
public string GetFileListContentForWBI()
{
#region Files
//If a file is read/write then it's delete automatically
bool bFilesReadOnly = AyaBizUtils.Right("Object.AyaFile") < (int)SecurityLevelTypes.ReadWrite;
bool bFilesVisible = AyaBizUtils.Right("Object.AyaFile") > (int)SecurityLevelTypes.NoAccess;
System.Text.StringBuilder sbFiles = new StringBuilder();
if (bFilesVisible)
{
//Build up file list / links
AyaFileList fl = AyaFileList.GetList(this.ID);
if (!(fl.Count == 0))
{
sbFiles.Append("<ul>");
foreach (AyaFileList.AyaFileListInfo i in fl)
{
if (i.FileType != AyaFileType.EmbeddedWikiImage)
{
sbFiles.Append("<li><a href=\"AyaFileHandler.ashx?id=");
sbFiles.Append(i.LT_O_AyaFile.Value.ToString());
sbFiles.Append("\">");
sbFiles.Append(i.LT_O_AyaFile.Display);
sbFiles.Append("</a>");
sbFiles.Append("&nbsp;-&nbsp;");
sbFiles.Append(i.LT_AyaFile_Label_FileSize);
sbFiles.Append("&nbsp;&nbsp;&nbsp;[");
sbFiles.Append(i.LT_Common_Label_Creator.Display);
sbFiles.Append("&nbsp;");
sbFiles.Append(i.LT_Common_Label_Created);
sbFiles.Append("]&nbsp;");
//can delete?
if (!bFilesReadOnly)
{//<a href="http://www.ayanova.com" onClick="return confirm_delete();"><img src="graphics/Delete16.gif"/></a>
//put a delete link on the end of the file list item
sbFiles.Append("<a href=\"AyaFileHandler.ashx?id=");
sbFiles.Append(i.LT_O_AyaFile.Value.ToString());
sbFiles.Append("&d=1\" onClick=\"return confirm_delete();\">");
sbFiles.Append("<img src=\"graphics/Delete16.png\">");
sbFiles.Append("</a>");
}
//close list item tag
sbFiles.Append("</li>");
}
}
sbFiles.Append("</ul>");
}
}
return sbFiles.ToString();
#endregion files
}
#region File list data structure
#pragma warning disable 1591
/// <summary>
/// Properties
/// </summary>
[Serializable]
public struct WikiFileInfo
{
public string Name { get; set; }
public string Id { get; set; }
public string Size { get; set; }
public string Creator { get; set; }
public string Created { get; set; }
}//end WikiFileInfo
#pragma warning restore 1591
#endregion
/// <summary>
/// Flag indicating if current user can edit / delete wiki files
/// </summary>
public bool WikiFileReadOnly
{
get
{
return AyaBizUtils.Right("Object.AyaFile") < (int)SecurityLevelTypes.ReadWrite; ;
}
}
/// <summary>
/// Fetches file list based on files in current wiki page
/// </summary>
/// <returns></returns>
public List<WikiFileInfo> GetFileListContentForRI()
{
#region Files
bool bFilesVisible = AyaBizUtils.Right("Object.AyaFile") > (int)SecurityLevelTypes.NoAccess;
List<WikiFileInfo> ret = new List<WikiFileInfo>();
if (bFilesVisible)
{
//Build up file list / links
AyaFileList fl = AyaFileList.GetList(this.ID);
if (!(fl.Count == 0))
{
foreach (AyaFileList.AyaFileListInfo i in fl)
{
if (i.FileType != AyaFileType.EmbeddedWikiImage)
{
WikiFileInfo fi = new WikiFileInfo();
fi.Id = i.LT_O_AyaFile.Value.ToString();
fi.Name = i.LT_O_AyaFile.Display;
fi.Size = i.LT_AyaFile_Label_FileSize;
fi.Creator = i.LT_Common_Label_Creator.Display;
fi.Created = i.LT_Common_Label_Created.ToString();
ret.Add(fi);
}
}
}
}
return ret;
#endregion files
}
/// <summary>
/// Set the content based on string
/// from WBI's html editor for wiki pages
/// (fixes up image tags)
/// </summary>
/// <param name="sContent"></param>
public void SetContentFromWBI(string sContent)
{
//convert the image links
//parse out the image links and convert them to AyaNova style image links
MatchCollection mc = AyaBizUtils.rxImages.Matches(sContent);
foreach (Match m in mc)
{
//parse out id
string sID = AyaBizUtils.rxGuid.Match(m.Value).Value;
if (!string.IsNullOrEmpty(sID))
sContent = sContent.Replace(m.Value, "[AyaImage:" + sID + "]");
}
#region Convert font sizes
//THIS WAS NOT REQUIRED AS THERE IS A CONTENT FILTER IN THE RADEDIT CONTROL
//TO NOT CONVERT FONTS TO SPANS WHICH FIXED THE PROBLEM
//BUT KEEPING FOR FUTURE REFERENCE JUST IN CASE
//convert the font sizes from pixels to points
//this is a requirement only because the easybyte rtf2html control
//goes snaky if the font sizes are specified in pixels
//This is what it likes: <font SIZE="2"
//this is what drives it nuts: font-size: 13px;
// Points Pixels Ems Percent
//6pt 8px 0.5em 50%
//7pt 9px 0.55em 55%
//7.5pt 10px 0.625em 62.5%
//8pt 11px 0.7em 70%
//9pt 12px 0.75em 75%
//10pt 13px 0.8em 80%
//10.5pt 14px 0.875em 87.5%
//11pt 15px 0.95em 95%
//12pt 16px 1em 100%
//13pt 17px 1.05em 105%
//13.5pt 18px 1.125em 112.5%
//14pt 19px 1.2em 120%
//14.5pt 20px 1.25em 125%
//15pt 21px 1.3em 130%
//16pt 22px 1.4em 140%
//17pt 23px 1.45em 145%
//18pt 24px 1.5em 150%
//20pt 26px 1.6em 160%
//22pt 29px 1.8em 180%
//24pt 32px 2em 200%
//26pt 35px 2.2em 220%
//27pt 36px 2.25em 225%
//28pt 37px 2.3em 230%
//29pt 38px 2.35em 235%
//30pt 40px 2.45em 245%
//32pt 42px 2.55em 255%
//34pt 45px 2.75em 275%
//36pt 48px 3em 300%
#endregion
SetContent(sContent);
}
/// <summary>
/// Get the content in it's native Unicode encoded HTML format
///
/// </summary>
public string GetContentAsString
{
get
{
return Encoding.Unicode.GetString(AyaBizUtils.Decompress(mContent));
}
}
/// <summary>
/// Get the content as a memory stream
/// </summary>
/// <returns></returns>
public System.IO.MemoryStream GetContent()
{
return new System.IO.MemoryStream(AyaBizUtils.Decompress(mContent));
}
/// <summary>
/// Set the content based on string
/// </summary>
/// <param name="sContent"></param>
public void SetContent(string sContent)
{
using (MemoryStream ms = new MemoryStream(System.Text.Encoding.Unicode.GetBytes(sContent)))
{
SetContent(ms);
}
}
/// <summary>
/// Set the content from memory stream
/// </summary>
/// <param name="mStream"></param>
public void SetContent(System.IO.MemoryStream mStream)
{
mStream.Position = 0;
//byte[] bData = new byte[mStream.Length + 1];//bugbug? Could this be wrong and resulting in the extra characters in the page?
byte[] bData = new byte[mStream.Length];
mStream.Read(bData, 0, (int)mStream.Length);
mContent = AyaBizUtils.Compress(bData);
MarkDirty();
}
/// <summary>
/// Flag - indicates if current user can open the wiki page for this object
/// See <see cref="WikiPage.ShowWikiLink(RootObjectTypes, Guid)"/> method for details
///
/// This is cached for the lifetime of this object
/// </summary>
public bool CanWiki//case 73
{
get
{
if (!bCanWiki.HasValue)
bCanWiki = WikiPage.ShowWikiLink(RootObjectTypes.WikiPage, mID);
return bCanWiki.Value;
}
}
//cache the result in case the UI calls this repeatedly
private bool? bCanWiki = null;
/// <summary>
/// Throw an error when a read only user
/// tries to set a property
/// (this should normally never be called unless someone is using the developer api since the UI
/// should prevent it from happening initially)
/// </summary>
private void ThrowSetError()
{
throw new System.Security.SecurityException
(
string.Format
(
LocalizedTextTable.GetLocalizedTextDirect("Error.Security.NotAuthorizedToChange"),
LocalizedTextTable.GetLocalizedTextDirect("O.WikiPage")
)
);
}
#endregion
#region System.object overrides
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return "WikiPage" + mID.ToString();
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(Object obj)
{
if (obj == null || GetType() != obj.GetType()) return false;
WikiPage c = (WikiPage)obj;
return mID == c.mID;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return ("WikiPage" + mID).GetHashCode();
}
#endregion
#region Searching
/// <summary>
/// Returns a search result object based on search terms
/// for the ID specified
/// </summary>
/// <param name="ID"></param>
/// <param name="searchTerms"></param>
/// <returns></returns>
public static SearchResult GetSearchResult(Guid ID, string[] searchTerms)
{
if (AyaBizUtils.Right("Object.WikiPage") < (int)SecurityLevelTypes.ReadOnly)
return new SearchResult();
SearchResult sr = new SearchResult();
WikiPage w = null;
try
{
w = WikiPage.GetItem(ID);
}
catch
{
//some kind of failure then return empty sr
return sr;
}
System.Text.StringBuilder sb = new System.Text.StringBuilder();
//Get description for later adding to description string
sr.Description = NameFetcher.GetItem(new TypeAndID(w.RootObjectType, w.RootObjectID)).RecordName;
sr.AncestorRootObjectID = w.RootObjectID; ;
sr.AncestorRootObjectType = w.RootObjectType;
sr.Created = w.mCreated;
sr.Modified = w.mModified;
sr.Creator = w.Creator;
sr.Modifier = w.Modifier;
//Case 58
//Is the document's ancestor within their region?
if (
!AyaBizUtils.InYourRegion(
ObjectRegionIDFetcher.ObjectRegion(
new TypeAndID(
sr.AncestorRootObjectType, sr.AncestorRootObjectID
)
)
)
) return new SearchResult();//case 58
//Security check..do they have rights to the ancestor object?
//BUGBUG case 1387, as a workaround just allow it if it's of type workorder
if (sr.AncestorRootObjectType != RootObjectTypes.Workorder)
if (AyaBizUtils.Right("Object." + sr.AncestorRootObjectType.ToString()) < (int)SecurityLevelTypes.ReadOnly)
return new SearchResult();
//Formulate results
sb.Append(w.Title);
sb.Append(" ");
sb.Append(w.GetContentAsPlainText.Replace("\r\n", " "));
ExtractAndRank er = new ExtractAndRank();
er.Process(sb.ToString().Trim(), searchTerms);
sr.Extract = er.Extract;
sr.Rank = er.Ranking;
//flip back to what should be opened
sr.AncestorRootObjectID = ID;
sr.AncestorRootObjectType = RootObjectTypes.WikiPage;
return sr;
}
#endregion
#region Static methods
#region rights
/// <summary>
/// Indicates whether user has rights to view a wiki page given the TypeAndID object
///
/// </summary>
/// <param name="tid">Object the wiki page want's to be viewed for</param>
/// <returns>true if can view and should show link to view</returns>
public static bool ShowWikiLink(TypeAndID tid)
{
return ShowWikiLink(tid.RootObjectType, tid.ID);
}
/// <summary>
/// Indicates whether user has rights to view a wiki page given the object type and id
/// Checks wikipage rights, global wiki page rights, checks if their own user wiki page and finally checks object rights
/// </summary>
/// <param name="oType">Object type who's wiki page is desired to be viewed</param>
/// <param name="RootObjectID">ID of object</param>
/// <returns>true if can view and should show link to view</returns>
public static bool ShowWikiLink(RootObjectTypes oType, Guid RootObjectID)
{
bool AllowedToViewPage = (AyaBizUtils.Right("Object.WikiPage") > (int)SecurityLevelTypes.NoAccess);
bool AllowedToViewGlobalWikiPage = (AyaBizUtils.Right("Object.GlobalWikiPage") > (int)SecurityLevelTypes.NoAccess);
//Special right - GlobalWikiPage supersedes all other rights
if (oType == RootObjectTypes.Global)
{
return AllowedToViewGlobalWikiPage;
}
//Can they view non global wiki pages at all
if (!AllowedToViewPage)
return false;
//Special right - their own wiki page
if (oType == RootObjectTypes.User && RootObjectID == User.CurrentThreadUserID)
{
//Users can *always* view their own personal wiki page if they have rights to view wiki pages
return true;
}
//From here on rights will follow biz object rights
//If they are allowed to view the object they are allowed to view the wiki page
//then Bob's your lobster
if (AyaBizUtils.Right(oType) > (int)SecurityLevelTypes.NoAccess)
return true;
return false;
}
/// <summary>
/// Indicates whether user has rights to edit the wiki page for the object in question
/// </summary>
/// <param name="tid">TypeAndID combined of object who's wiki page is desired to be edited</param>
/// <returns></returns>
public static bool CanEditWikiPage(TypeAndID tid)
{
return CanEditWikiPage(tid.RootObjectType, tid.ID);
}
/// <summary>
/// Indicates whether user has rights to edit the wiki page for the object in question
/// </summary>
/// <param name="oType">Object type who's wiki page is desired to be edited</param>
/// <param name="RootObjectID">ID of object</param>
/// <returns></returns>
public static bool CanEditWikiPage(RootObjectTypes oType, Guid RootObjectID)
{
//If you can't even view it you certainly can't edit it
if (!ShowWikiLink(oType, RootObjectID)) return false;
bool AllowedToEditPage = (AyaBizUtils.Right("Object.WikiPage") > (int)SecurityLevelTypes.ReadOnly);
bool AllowedToEditGlobalWikiPage = (AyaBizUtils.Right("Object.GlobalWikiPage") > (int)SecurityLevelTypes.ReadOnly);
//Special right - GlobalWikiPage supersedes all other rights
if (oType == RootObjectTypes.Global)
{
return AllowedToEditGlobalWikiPage;
}
//Can they edit non global wiki pages at all
if (!AllowedToEditPage)
return false;
//Special right - their own wiki page
if (oType == RootObjectTypes.User && RootObjectID == User.CurrentThreadUserID)
{
//Users can *always* edit their own personal wiki page if they have rights to edit wiki pages
return true;
}
//From here on rights will follow biz object rights
//If they are allowed to edit the object they are allowed to edit the wiki page
//then Bob's your lobster
if (AyaBizUtils.Right(oType) > (int)SecurityLevelTypes.ReadOnly)
return true;
return false;
}
#endregion rights
/// <summary>
/// Fetch existing WikiPage
///
/// </summary>
/// <returns>WikiPage</returns>
/// <param name="ID">WikiPage Guid</param>
public static WikiPage GetItem(Guid ID)
{
return (WikiPage)DataPortal.Fetch(new Criteria(ID, null, null, RootObjectTypes.Nothing));
}
/// <summary>
/// Fetch existing WikiPage for object type and id specified
/// if none exists it will be created
/// </summary>
/// <returns>WikiPage</returns>
/// <param name="tid">Type and ID of object page is attached to</param>
/// <example><code>
/// //Get a wiki page for a specified client
/// WikiPage wp = WikiPage.GetItem(new TypeAndID(RootObjectTypes.Client, ClientID));
/// </code></example>
public static WikiPage GetItem(TypeAndID tid)
{
//case 1975
//added because this should have always been here
//to lighten client code
if (tid.RootObjectType == RootObjectTypes.WikiPage)
{
return GetItem(tid.ID);
}
//case 1584
//all wikipages for the different workorder types are simply stored with object type "workorder"
//but that object type isn't used anymore post case 1387 so this will be called with the exact workorder type
//so if it's an exact type translate back to simply workorder
RootObjectTypes actualWorkorderType = tid.RootObjectType;
switch (tid.RootObjectType)
{
case RootObjectTypes.WorkorderService:
case RootObjectTypes.WorkorderServiceTemplate:
case RootObjectTypes.WorkorderQuote:
case RootObjectTypes.WorkorderQuoteTemplate:
case RootObjectTypes.WorkorderPreventiveMaintenance:
case RootObjectTypes.WorkorderPreventiveMaintenanceTemplate:
actualWorkorderType = tid.RootObjectType;
tid = new TypeAndID(RootObjectTypes.Workorder, tid.ID);
break;
}
WikiPage w = (WikiPage)DataPortal.Fetch(new Criteria(Guid.Empty, null, tid, actualWorkorderType));
w.mExactObjectType = actualWorkorderType;
if (w.IsNew)
w = (WikiPage)w.Save();
return w;
}
/// <summary>
/// Delete WikiPage
/// </summary>
/// <param name="ID">WikiPage applicationID</param>
public static void DeleteItem(Guid ID)
{
DataPortal.Delete(new Criteria(ID, null, null, RootObjectTypes.Nothing));
}
/// <summary>
/// Used internally and called by DBUtil.RemoveDocs
/// which in turn is called by root objects when they are deleted
/// </summary>
/// <param name="ID"></param>
/// <param name="transaction"></param>
internal static void DeleteItem(Guid ID, IDbTransaction transaction)
{
DataPortal.Delete(new Criteria(ID, transaction, null, RootObjectTypes.Nothing));
}
/// <summary>
/// Check if given object Id has an associated wikipage or not
/// </summary>
public static bool HasWiki(Guid objectId)
{
return WikiPageExistanceChecker.WikiPageExists(objectId);
}
//case 3160
/// <summary>
/// Copy existing wiki page to destination type and id specified
/// If destination already has a wiki page or source id is not found nothing will happen and it will return false
/// NOTE: this is not a public method because it could break so many things if misused
/// </summary>
/// <param name="SourceTypeAndId">Source object with wiki page to be copied</param>
/// <param name="DestTypeAndId">Type and Id of destination object to receive wikipage</param>
/// <returns>false on any fail, true on success</returns>
internal static bool DuplicateItem(TypeAndID SourceTypeAndId, TypeAndID DestTypeAndId)
{
//Not valid to copy to
if (DestTypeAndId.RootObjectType == RootObjectTypes.WikiPage)
{
throw new System.NotSupportedException("Wikipage is not a valid destination object type for duplicate item");
}
if (HasWiki(DestTypeAndId.ID))
{
throw new System.NotSupportedException("Destination already has a Wikipage");
}
WikiPage src = WikiPage.GetItem(SourceTypeAndId);
if (src.IsNew)
{
throw new System.NotSupportedException("Source Wikipage specified does not exist");
}
//case 1584
//all wikipages for the different workorder types are simply stored with object type "workorder"
//but that object type isn't used anymore post case 1387 so this will be called with the exact workorder type
//so if it's an exact type translate back to simply workorder
RootObjectTypes actualWorkorderType = DestTypeAndId.RootObjectType;
switch (DestTypeAndId.RootObjectType)
{
case RootObjectTypes.WorkorderService:
case RootObjectTypes.WorkorderServiceTemplate:
case RootObjectTypes.WorkorderQuote:
case RootObjectTypes.WorkorderQuoteTemplate:
case RootObjectTypes.WorkorderPreventiveMaintenance:
case RootObjectTypes.WorkorderPreventiveMaintenanceTemplate:
actualWorkorderType = DestTypeAndId.RootObjectType;
DestTypeAndId = new TypeAndID(RootObjectTypes.Workorder, DestTypeAndId.ID);
break;
}
WikiPage w = (WikiPage)DataPortal.Fetch(new Criteria(Guid.Empty, null, DestTypeAndId, actualWorkorderType));
w.mExactObjectType = actualWorkorderType;
//copy fields
//through property to trigger dirty-ness
w.Title = src.Title;
w.mContent = src.mContent;
//copy files
AyaFileList fl = AyaFileList.GetList(src.ID);
if (!(fl.Count == 0))
{
foreach (AyaFileList.AyaFileListInfo i in fl)
{
if (i.FileType != AyaFileType.EmbeddedWikiImage)
{
//copy file over to new wikipage
AyaFile afsrc=AyaFile.GetItem(i.LT_O_AyaFile.Value);
AyaFile af = AyaFile.NewItem();
af.RootObjectID = w.ID;
af.RootObjectType = RootObjectTypes.WikiPage;
af.Name = afsrc.Name;
af.FileType = AyaFileType.WikiFile;
af.SetContent(afsrc.GetContent());
af.Save();
}
}
}
if (w.IsNew)
w = (WikiPage)w.Save();
return true;
}
#endregion
#region Utils
/// <summary>
/// Add a file to wikipage
/// </summary>
/// <param name="sPath">Full path to file (any format acceptible to File.ReadAllBytes)</param>
public void AddFile(string sPath)
{
if (File.Exists(sPath))
{
AyaFile af = AyaFile.NewItem();
af.RootObjectID = this.ID;
af.RootObjectType = RootObjectTypes.WikiPage;
af.Name = Path.GetFileName(sPath);
af.FileType = AyaFileType.WikiFile;
af.SetContent(File.ReadAllBytes(sPath));
af.Save();
}
}
#endregion
#region DAL DATA ACCESS
#region Fetch
///
/// <param name="Criteria"></param>
protected override void DataPortal_Fetch(object Criteria)
{
Criteria crit = (Criteria)Criteria;
SafeDataReader dr = null;
try
{
if (crit.TID != null)
{
dr = DBUtil.GetReaderFromSQLString("SELECT * FROM aWikiPage WHERE aRootObjectID=@ID;", crit.TID.ID);
if (!dr.Read())
{
if (dr != null) dr.Close();
//Ok, there is no existing wikipage for this object
//So create a new empty one and return it
mRootObjectID = crit.TID.ID;
mRootObjectType = crit.TID.RootObjectType;
//case 1632
string defaultText = "";
if (crit.ActualObjectType != RootObjectTypes.Nothing)
defaultText = LocalizedTextTable.GetLocalizedTextDirect("O.WikiPage") + " - " + NameFetcher.GetItem(new TypeAndID(crit.ActualObjectType, crit.TID.ID)).RecordName;
else
defaultText = LocalizedTextTable.GetLocalizedTextDirect("O.WikiPage") + " - " + NameFetcher.GetItem(crit.TID).RecordName;
this.SetContent(defaultText);
bReadOnly = AyaBizUtils.Right("Object.WikiPage") < (int)SecurityLevelTypes.ReadWrite;
return;
}
}
else
{
dr = DBUtil.GetReaderFromSQLString("SELECT * FROM aWikiPage WHERE aID=@ID;", crit.ID);
if (!dr.Read())
DBUtil.ThrowFetchError("WikiPage ID: " + crit.ID.ToString());
}
//Standard fields
mID = dr.GetGuid("aID");
mCreated = DBUtil.ToLocal(dr.GetSmartDate("aCreated"));
mModified = DBUtil.ToLocal(dr.GetSmartDate("aModified"));
mCreator = dr.GetGuid("aCreator");
mModifier = dr.GetGuid("aModifier");
//WikiPage fields
mInternalOnly = dr.GetBoolean("AINTERNALONLY");
Title = dr.GetString("aTitle");
RootObjectID = dr.GetGuid("aRootObjectID");
RootObjectType = (RootObjectTypes)dr.GetInt16("aRootObjectType");
//Get the WObject
//Get the data's size
int nSize = dr.GetInt32("aWObjectSize");
//allocate a place to store it
mContent = new Byte[nSize];
//retrieve the (compressed) bytes
dr.GetBytes("aWObject", 0, mContent, 0, mContent.Length);
if (dr != null) dr.Close();
}
finally
{
if (dr != null) dr.Close();
}
MarkOld();
//Get access rights level
bReadOnly = AyaBizUtils.Right("Object.WikiPage") < (int)SecurityLevelTypes.ReadWrite;
}
#endregion fetch
#region Update
/// <summary>
/// Called by DataPortal to delete/add/update data into the database
/// </summary>
protected override void DataPortal_Update()
{
// If not a new record, check if record was modified
//by another user since original retrieval:
if (!IsNew)
DBUtil.CheckSafeToUpdate(this.mModified.Date, this.mID, "aWikiPage");
#region Delete
if (IsDeleted)
{
throw new System.NotSupportedException("WikiPage->Update->Delete: not supported for this object, call the static/shared WikiPage.DeleteItem() method instead");
}
#endregion
#region Add / Update
//get modification time temporarily, if update succeeds then
//set to this time
System.DateTime dtModified = DBUtil.CurrentWorkingDateTime;
DBCommandWrapper cm = null;
if (IsNew)//Add or update?
cm = DBUtil.GetCommandFromSQL(
"INSERT INTO aWikiPage (aID, aInternalOnly, aCreated,aModified,aCreator,aModifier, " +
"aRootObjectID, aRootObjectType, aTitle,aWObject,aWObjectSize) " +
"VALUES (@ID,@InternalOnly,@Created,@Modified,@CurrentUserID,@CurrentUserID, " +
"@RootObjectID,@RootObjectType, @Title,@WObject,@WObjectSize)"
);
else
cm = DBUtil.GetCommandFromSQL(
"UPDATE aWikiPage SET aID=@ID, " +
"aInternalOnly=@InternalOnly,aModified=@Modified,aModifier=@CurrentUserID, " +
"aRootObjectID=@RootObjectID,aRootObjectType=@RootObjectType,aTitle=@Title, " +
"aWObject=@WObject,aWObjectSize=@WObjectSize " +
"WHERE aID=@ID"
);
//WikiPage fields
cm.AddInParameter("@ID", DbType.Guid, mID);
cm.AddInParameter("@InternalOnly", DbType.Boolean, mInternalOnly);
cm.AddInParameter("@Title", DbType.String, mTitle);
cm.AddInParameter("@RootObjectID", DbType.Guid, mRootObjectID);
cm.AddInParameter("@RootObjectType", DbType.Int16, (int)mRootObjectType);
//Standard fields
cm.AddInParameter("@CurrentUserID", DbType.Guid, CurrentUserID);
cm.AddInParameter("@Created", DbType.DateTime, DBUtil.ToUTC(mCreated).DBValue);
cm.AddInParameter("@Modified", DbType.DateTime, DBUtil.ToUTC(dtModified));
cm.AddInParameter("@WObject", DbType.Object, mContent);
cm.AddInParameter("@WObjectSize", DbType.Int32, mContent.GetLength(0));
using (IDbConnection connection = DBUtil.DB.GetConnection())
{
connection.Open();
IDbTransaction transaction = connection.BeginTransaction();
try
{
DBUtil.DB.ExecuteNonQuery(cm, transaction);
//Process keywords
DBUtil.ProcessKeywords(transaction, this.mID, RootObjectTypes.WikiPage, IsNew, AyaBizUtils.Break(false, GetContentAsPlainText));
MarkOld();//db is now synched with object
// Commit the transaction
transaction.Commit();
}
catch
{
// Rollback transaction
transaction.Rollback();
throw;
}
finally
{
connection.Close();
}
//Successful update so
//change modification time to match
this.mModified.Date = dtModified;
}
#endregion
}
#endregion update
#region Delete
/// <summary>
/// Remove a WikiPage record, this is recursive in order to get the whole tree of potential pages .
/// </summary>
/// <param name="Criteria"></param>
protected override void DataPortal_Delete(object Criteria)
{
Criteria crit = (Criteria)Criteria;
//Get a list of all child pages that link to this page
List<Guid> l = new List<Guid>();
SafeDataReader dr = DBUtil.GetReaderFromSQLString(
"SELECT aID " +
"FROM aWikiPage WHERE aRootObjectID=@ID", crit.ID);
while (dr.Read())
{
l.Add(dr.GetGuid("aID"));
}
dr.Close();
//Delete object and child objects
DBCommandWrapper cmDelete = DBUtil.GetCommandFromSQL("DELETE FROM aWikiPage WHERE aID = @ID;");
cmDelete.AddInParameter("@ID", DbType.Guid, crit.ID);
DBCommandWrapper cmDeleteFiles = DBUtil.GetCommandFromSQL("DELETE FROM aFile WHERE aRootObjectID = @ID;");
cmDeleteFiles.AddInParameter("@ID", DbType.Guid, crit.ID);
using (IDbConnection connection = DBUtil.DB.GetConnection())
{
connection.Open();
IDbTransaction transaction = null;
if (crit.transaction != null)
transaction = crit.transaction;
else
transaction = connection.BeginTransaction();
try
{
//Call delete on children first so that the deletion process goes from bottom of tree upwards
//and if there is a failure at any point the topmost page is still present for the user to attempt a
//delete on again or whatever. No orphaned records this way
foreach (Guid g in l)
{
WikiPage.DeleteItem(g, transaction);
}
DBUtil.DB.ExecuteNonQuery(cmDeleteFiles, transaction);
DBUtil.DB.ExecuteNonQuery(cmDelete, transaction);
DBUtil.RemoveKeywords(transaction, RootObjectTypes.WikiPage, crit.ID);
// Commit the transaction if it was started here
if (crit.transaction == null)
transaction.Commit();
connection.Close();
//Successful deletion, now find all pages that have rootobjectid set to this pages ID
//and call delete on them hence recursively deleting the entire tree
}
catch
{
// Rollback transaction
transaction.Rollback();
throw;
}
finally
{
connection.Close();
}
}
}
#endregion delete
#endregion
#region Override IsValid / IsDirty
/// <summary>
///
/// </summary>
public override bool IsValid
{
get
{
return base.IsValid;
}
}
/// <summary>
///
/// </summary>
public override bool IsDirty
{
get
{
return base.IsDirty;
}
}
#endregion
#region criteria
/// <summary>
/// Criteria for identifying existing object
/// </summary>
[Serializable]
private class Criteria
{
public Guid ID;
public IDbTransaction transaction;//used for delete when called from root object being deleted in DBUtil.RemoveDocs
public TypeAndID TID;
public RootObjectTypes ActualObjectType;
public Criteria(Guid _ID, IDbTransaction _transaction, TypeAndID _TID, RootObjectTypes _actualObjectType)
{
ID = _ID;
transaction = _transaction;
TID = _TID;
ActualObjectType = _actualObjectType;
}
}
#endregion
}//end WikiPage
}//end namespace GZTW.AyaNova.BLL