/////////////////////////////////////////////////////////// // 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 { /// /// WikiPage object /// /// [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 /// /// Private constructor to prevent direct instantiation /// 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 /// /// Internal Unique GUID value of WikiPage record in database /// public Guid ID { get { return mID; } } /// /// Get created date /// /// /// public string Created { get { return mCreated.ToString(); } } /// /// Get modified date /// /// /// public string Modified { get { return mModified.ToString(); } } /// /// Get user record ID of person who created this record /// /// /// public Guid Creator { get { return mCreator; } } /// /// Get user ID of person who modified this record /// /// /// public Guid Modifier { get { return mModifier; } } /// /// AyaNova object ID /// 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(); } } } } /// /// AyaNova object type /// 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(); } } } } /// /// 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. /// public RootObjectTypes ExactRootObjectType { get { return mExactObjectType; } } /// ///If true (default) then only visible to AyaNova usertypes who are not ///a client. /// public bool InternalOnly { get { return mInternalOnly; } set { if (bReadOnly) ThrowSetError(); else { if (mInternalOnly != value) { mInternalOnly = value; MarkDirty(); } } } } /// /// Title displayed on wiki page if set 1-255 Unicode characters /// 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(); } } } } /// /// Internal flag set from AyaNova / WBI when content is dirty /// but WikiPage hasn't had it's content updated yet /// public bool HasDirtyContent { set { if (value == true) MarkDirty(); } } /// /// Indicates whether page has content or not /// public bool HasContent { get { return mContent.GetLength(0) > 0; } } /// /// Get the content of the wiki page which is stored in HTML /// format natively as plain text /// public string GetContentAsPlainText { get { string s = GetContentAsString; s = s.Replace("
", "\r\n"); s = s.Replace("
", "\r\n"); s = System.Text.RegularExpressions.Regex.Replace(s, "<[^>]+?>", ""); s = AyaBizUtils.rxAyaImageTags.Replace(s, ""); s = AyaBizUtils.rxAyaLinks.Replace(s, ""); s = s.Replace(" ", " "); s = s.Replace(">", ">"); s = s.Replace("<", "<"); return s; } } /// /// Get's content with WBI ready AyaImage: tags and AyaNova: urls /// /// true=don't build file list /// public string GetContentAsWBIReadyHTML(bool bForEditing) { string s = GetContentAsString; MatchCollection mc = null; List list = new List(); #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 listProtocoled = new List(); List listEmail = new List(); List listWeb = new List(); //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, "" + surl + ""); foreach (string surl in listEmail) s = s.Replace(surl, "" + surl + ""); foreach (string surl in listWeb) s = s.Replace(surl, "" + surl + ""); //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, ""); } #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, "" + name + ""); } } } #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, "" + m.Value + ""); } } #endregion return s; } /// /// Get's content with AyaNova RI ready AyaImage: tags and AyaNova: urls /// /// true=don't build file list /// /// public string GetContentAsRIReadyHTML(bool bForEditing, string baseSiteURL) { string s = GetContentAsString; MatchCollection mc = null; List list = new List(); #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 listProtocoled = new List(); List listEmail = new List(); List listWeb = new List(); //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, "" + surl + ""); foreach (string surl in listEmail) s = s.Replace(surl, "" + surl + ""); foreach (string surl in listWeb) s = s.Replace(surl, "" + surl + ""); //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, ""); } #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, "" + name + ""); } } } #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, "" + m.Value + ""); } } #endregion RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Singleline; Regex rxBody = new Regex("(?.*)", options); Match matchBody = rxBody.Match(s); string theBody = string.Empty; Regex rxStyle = new Regex(".*)", 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 "", options); Match matchStyle = rxStyle.Match(s); string theStyle = string.Empty; if (matchStyle.Success) { theStyle = matchStyle.Groups["theStyle"].Value; return ""; } /// /// Fetches HTML fragment for file list area of web page in WBI based on files in current wiki page /// /// 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("
    "); foreach (AyaFileList.AyaFileListInfo i in fl) { if (i.FileType != AyaFileType.EmbeddedWikiImage) { sbFiles.Append("
  • "); sbFiles.Append(i.LT_O_AyaFile.Display); sbFiles.Append(""); sbFiles.Append(" - "); sbFiles.Append(i.LT_AyaFile_Label_FileSize); sbFiles.Append("   ["); sbFiles.Append(i.LT_Common_Label_Creator.Display); sbFiles.Append(" "); sbFiles.Append(i.LT_Common_Label_Created); sbFiles.Append("] "); //can delete? if (!bFilesReadOnly) {// //put a delete link on the end of the file list item sbFiles.Append(""); sbFiles.Append(""); sbFiles.Append(""); } //close list item tag sbFiles.Append("
  • "); } } sbFiles.Append("
"); } } return sbFiles.ToString(); #endregion files } #region File list data structure #pragma warning disable 1591 /// /// Properties /// [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 /// /// Flag indicating if current user can edit / delete wiki files /// public bool WikiFileReadOnly { get { return AyaBizUtils.Right("Object.AyaFile") < (int)SecurityLevelTypes.ReadWrite; ; } } /// /// Fetches file list based on files in current wiki page /// /// public List GetFileListContentForRI() { #region Files bool bFilesVisible = AyaBizUtils.Right("Object.AyaFile") > (int)SecurityLevelTypes.NoAccess; List ret = new List(); 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 } /// /// Set the content based on string /// from WBI's html editor for wiki pages /// (fixes up image tags) /// /// 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: /// Get the content in it's native Unicode encoded HTML format /// /// public string GetContentAsString { get { return Encoding.Unicode.GetString(AyaBizUtils.Decompress(mContent)); } } /// /// Get the content as a memory stream /// /// public System.IO.MemoryStream GetContent() { return new System.IO.MemoryStream(AyaBizUtils.Decompress(mContent)); } /// /// Set the content based on string /// /// public void SetContent(string sContent) { using (MemoryStream ms = new MemoryStream(System.Text.Encoding.Unicode.GetBytes(sContent))) { SetContent(ms); } } /// /// Set the content from memory stream /// /// 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(); } /// /// Flag - indicates if current user can open the wiki page for this object /// See method for details /// /// This is cached for the lifetime of this object /// 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; /// /// 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) /// private void ThrowSetError() { throw new System.Security.SecurityException ( string.Format ( LocalizedTextTable.GetLocalizedTextDirect("Error.Security.NotAuthorizedToChange"), LocalizedTextTable.GetLocalizedTextDirect("O.WikiPage") ) ); } #endregion #region System.object overrides /// /// /// /// public override string ToString() { return "WikiPage" + mID.ToString(); } /// /// /// /// /// public override bool Equals(Object obj) { if (obj == null || GetType() != obj.GetType()) return false; WikiPage c = (WikiPage)obj; return mID == c.mID; } /// /// /// /// public override int GetHashCode() { return ("WikiPage" + mID).GetHashCode(); } #endregion #region Searching /// /// Returns a search result object based on search terms /// for the ID specified /// /// /// /// 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 /// /// Indicates whether user has rights to view a wiki page given the TypeAndID object /// /// /// Object the wiki page want's to be viewed for /// true if can view and should show link to view public static bool ShowWikiLink(TypeAndID tid) { return ShowWikiLink(tid.RootObjectType, tid.ID); } /// /// 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 /// /// Object type who's wiki page is desired to be viewed /// ID of object /// true if can view and should show link to view 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; } /// /// Indicates whether user has rights to edit the wiki page for the object in question /// /// TypeAndID combined of object who's wiki page is desired to be edited /// public static bool CanEditWikiPage(TypeAndID tid) { return CanEditWikiPage(tid.RootObjectType, tid.ID); } /// /// Indicates whether user has rights to edit the wiki page for the object in question /// /// Object type who's wiki page is desired to be edited /// ID of object /// 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 /// /// Fetch existing WikiPage /// /// /// WikiPage /// WikiPage Guid public static WikiPage GetItem(Guid ID) { return (WikiPage)DataPortal.Fetch(new Criteria(ID, null, null, RootObjectTypes.Nothing)); } /// /// Fetch existing WikiPage for object type and id specified /// if none exists it will be created /// /// WikiPage /// Type and ID of object page is attached to /// /// //Get a wiki page for a specified client /// WikiPage wp = WikiPage.GetItem(new TypeAndID(RootObjectTypes.Client, ClientID)); /// 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; } /// /// Delete WikiPage /// /// WikiPage applicationID public static void DeleteItem(Guid ID) { DataPortal.Delete(new Criteria(ID, null, null, RootObjectTypes.Nothing)); } /// /// Used internally and called by DBUtil.RemoveDocs /// which in turn is called by root objects when they are deleted /// /// /// internal static void DeleteItem(Guid ID, IDbTransaction transaction) { DataPortal.Delete(new Criteria(ID, transaction, null, RootObjectTypes.Nothing)); } /// /// Check if given object Id has an associated wikipage or not /// public static bool HasWiki(Guid objectId) { return WikiPageExistanceChecker.WikiPageExists(objectId); } //case 3160 /// /// 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 /// /// Source object with wiki page to be copied /// Type and Id of destination object to receive wikipage /// false on any fail, true on success 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 /// /// Add a file to wikipage /// /// Full path to file (any format acceptible to File.ReadAllBytes) 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 /// /// 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 /// /// Called by DataPortal to delete/add/update data into the database /// 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 /// /// Remove a WikiPage record, this is recursive in order to get the whole tree of potential pages . /// /// protected override void DataPortal_Delete(object Criteria) { Criteria crit = (Criteria)Criteria; //Get a list of all child pages that link to this page List l = new List(); 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 /// /// /// public override bool IsValid { get { return base.IsValid; } } /// /// /// public override bool IsDirty { get { return base.IsDirty; } } #endregion #region criteria /// /// Criteria for identifying existing object /// [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