using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.ComponentModel; using System.Windows.Forms; using System.Reflection; using AyaNova.PlugIn; using GZTW.AyaNova.BLL; using System.IO; using System.IO.Compression; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Linq; using System.Net.Http; using System.Net.Http.Headers; //using HtmlAgilityPack; //using ReverseMarkdown; namespace AyaNova.PlugIn.V8 { class V8 : IAyaNovaPlugin { #region Plugin interface System.Resources.ResourceManager resman = null; private static List ObjectsWeCanDealWith = null; public string PluginName { get { return "Export to V8"; } } public string PluginVersion { get { return "7.6 (patch 3)"; } } public string About { get { return "AyaNova V8 export plugin\r\n" + "Built " + AyaNova.PlugIn.V8.Timestamp.BuildAt.ToString() + "\r\n" + "Copyright 2020 Ground Zero Tech-Works Inc."; } } public Guid PluginID { get { return new Guid("{73D7B77F-C96A-4198-9449-6529AFB6AA5B}"); } } public System.Drawing.Image PluginSmallIcon { get { return Resource1.Dump16; } } public System.Drawing.Image PluginLargeIcon { get { return Resource1.Dump32; } } public System.Resources.ResourceManager AyaNovaResourceManager { set { resman = value; } get { return resman; } } public bool Initialize(Version AyaNovaVersion, LocalizedTextTable localizedText) { if (AyaNovaVersion.Major < 7) { MessageBox.Show("The V8 export plugin requires AyaNova version 7.6 or newer"); return false; } if (AyaNovaVersion.Minor < 6) { MessageBox.Show("The V8 export plugin requires AyaNova version 7.6 or newer"); return false; } util.LocaleText = localizedText; ObjectsWeCanDealWith = new List(); ObjectsWeCanDealWith.Add(RootObjectTypes.Nothing); return true; } public void Close() { ; } public bool SingleObjectMenuShow(RootObjectTypes objectType) { return (ObjectsWeCanDealWith.Contains(objectType)); } public bool MultipleObjectsMenuShow(RootObjectTypes objectType) { return false; } public List SingleObjectMenuOptions(RootObjectTypes objectType, object ayaNovaObject) { if (!ObjectsWeCanDealWith.Contains(objectType)) return null; List list = new List(); list.Add(new AyaNovaPluginMenuItem("V8 Export", "Export to AyaNova 8 server", null, null)); return list; } public List MultipleObjectsMenuOptions(RootObjectTypes objectType) { return null; } public bool CommandSelectedForList(string commandKey, RootObjectTypes objectType, List objectIDList, object listObject) { return false; } public void CommandSelectedForSingleObject(string commandKey, RootObjectTypes objectType, object ayaNovaObject) { if (!User.CurrentUserIsAnAdministrator) { MessageBox.Show("This action can only be done by the AyaNova Administrator account"); return; } #if(!DEBUG) if (!AyaBizUtils.AyaNovaConnectionSetting.SingleUserConnection) { MessageBox.Show("** WARNING: before proceeding ensure no other users are logged into AyaNova to ensure the integrity of your exported data. Failing to do so *will* result in damaged data. ***"); MessageBox.Show("** WARNING: before proceeding make sure your AyaNova Generator service is STOPPED to ensure the integrity of your exported data. Failing to do so *will* result in damaged data. ***"); } #endif Auth d = new Auth(); var res = d.ShowDialog(); ; if (res == DialogResult.Cancel) { return; } //here because we logged in fine and can proceed //MessageBox.Show("Login successful! JWT is " + util.JWT); //Only one command DoExport(); } #endregion private Dictionary Map = new Dictionary(); private Dictionary TagMap = new Dictionary(); private Dictionary LocaleMap = new Dictionary(); private string ImportTag = "v7-import"; /// /// Dump the objects into a temporary directory as a series of JSON files /// then zip it all up into a single archive file and then erase the temporary folder /// private async void DoExport() { Cursor.Current = Cursors.WaitCursor; //Show progress form ProgressForm progress = new ProgressForm(); progress.Show(); progress.StartedImport(); progress.Append("Exporting data to AyaNova server @ " + util.ApiBaseUrl); try { progress.Op("Preparing to export...."); Map.Clear(); TagMap.Clear(); LocaleMap.Clear(); //admin user (not exported but is there already) Map.Add(User.AdministratorID, 1); //not sure if this is a good idea or not because in some cases would want to know if something is unexpectedly empty but will see for now //Map.Add(Guid.Empty, 0); /* TODO: * * todo: make up stub versions of all the core biz objects so can test a full wiki, attachment etc import todo: export docs, settle on "Export" and edit all references to Import to switch it around as it's probably confusing artifact from dbdump days * TODO: User: headofficeid, clientid vendorid, phone1, phone2, pagermaxtext * * * * * */ //if (progress.KeepGoing) // await ExportLocales(progress); //return; //Export in correct order: //ERASE DB progress.Op("Erasing AyaNova 8 data"); var a = await util.PostAsync("License/PermanentlyEraseAllData", "\"I understand\""); //TAGS progress.Op("Compiling tags"); if (progress.KeepGoing) ExportUnitModelCategories(progress); if (progress.KeepGoing) ExportUnitServiceTypes(progress); if (progress.KeepGoing) ExportWorkorderItemTypes(progress); if (progress.KeepGoing) ExportRegions(progress); if (progress.KeepGoing) ExportClientGroups(progress); if (progress.KeepGoing) ExportWorkorderCategories(progress); if (progress.KeepGoing) ExportPartCategories(progress); if (progress.KeepGoing) ExportScheduleableUserGroups(progress); if (progress.KeepGoing) ExportDispatchZones(progress); if (progress.KeepGoing) ExportUserSkills(progress); if (progress.KeepGoing) ExportUserCertifications(progress); progress.Op("Exporting objects"); //BIZ objects if (progress.KeepGoing) await ExportLocales(progress); if (progress.KeepGoing) await ExportUsers(progress); //dumpGlobalSettings(tempArchiveFolder, progress); //dumpSeedNumbers(tempArchiveFolder, progress); //dumpClients(tempArchiveFolder, progress); //dumpHeadOffices(tempArchiveFolder, progress); // //NOTE: when get to PRIORITY, or WORKORDER STATUS be sure to add color code as per already done in USER export if (progress.KeepGoing) { progress.Append("Export completed"); progress.Op(""); goto End; } progress.Append("Export cancelled before completing"); progress.Op(""); End: ; } catch (Exception ex) { progress.Append("ERROR, During operation: " + progress.LastOp + "\n" + progress.LastSubOp); progress.Append("\n************\nExport failed with error:"); progress.Append(ex.Message); progress.Append("stack:\n" + ex.StackTrace); } finally { progress.FinishedImport(); Cursor.Current = Cursors.Default; } //----------------------------------- //endof method } #region Object Export methods #region users private async System.Threading.Tasks.Task ExportUsers(ProgressForm progress) { //Step 1: export the CustomFields to FormCustom if applicable so that when doing individual items we can export their custom data too var ocf = ObjectHasCustomFieldDataToExport("User"); bool ShouldExportCustom = ocf != null; var DateCustomFields = await ExportCustomFieldSchema(ocf, "User", "User"); //Step 2: export the users UserPickList pl = UserPickList.GetList(false); progress.Append("Exporting " + pl.Count.ToString() + " Users"); #region Export administrator wiki and attached files if present { User admin = User.GetItem(User.AdministratorID); progress.Op("Administrator account"); //Attachments and wiki string AdminWikiContent = null; var hasWiki = WikiPage.HasWiki(User.AdministratorID); if (hasWiki) { await ExportAttachments(new TypeAndID(RootObjectTypes.User, User.AdministratorID), progress); AdminWikiContent = GetWikiContent(new TypeAndID(RootObjectTypes.User, admin.ID)); } string adminCustomFields = null; //Custom fields? if (ShouldExportCustom) { adminCustomFields = CustomFieldData(admin, DateCustomFields); } //check if we need to do anything with the manager account if (hasWiki || adminCustomFields != null) { //yes, so fetch it and modify it and put it back again var a = await util.GetAsync("User/1"); dynamic d = a.ObjectResponse["data"]; d.wiki = AdminWikiContent; d.customFields = CustomFieldData(admin, DateCustomFields); await util.PutAsync("User/1", d.ToString()); } } #endregion admin export foreach (UserPickList.UserPickListInfo i in pl) { if (!progress.KeepGoing) return; List tags = new List(); tags.Add(ImportTag); //skip administrator user fields //but do export administrator if (i.ID == User.AdministratorID) continue; User c = User.GetItem(i.ID); dynamic d = new JObject(); d.name = c.FirstName + " " + c.LastName; progress.Op("User " + d.name); d.userType = (int)c.UserType; //if special 3rd party user type then set their parent object id to -1 to signify will fill in later and satisfy biz rules at server switch (c.UserType) { case UserTypes.Client: d.customerId = -1; break; case UserTypes.HeadOffice: d.headOfficeId = -1; break; } if (c.VendorID != Guid.Empty) { d.userType = 7;//7 is the RAVEN user type for subcontractor d.subVendorId = -1; } d.active = false;//all imported users are inactive to start d.roles = 0;//todo: try to determine role from v7 member of group? or is that even possible? d.login = util.RandomString(); d.password = util.RandomString(); d.employeeNumber = c.EmployeeNumber; d.notes = c.Notes; Tagit(c.RegionID, tags); Tagit(c.DispatchZoneID, tags); foreach (UserSkillAssigned skill in c.UserSkills) { Tagit(skill.UserSkillID, tags); } foreach (UserCertificationAssigned cert in c.UserCertifications) { Tagit(cert.UserCertificationID, tags); } SetTags(d, tags); //Attachments and wiki var hasWiki = WikiPage.HasWiki(c.ID); if (hasWiki) { await ExportAttachments(new TypeAndID(RootObjectTypes.User, c.ID), progress); d.wiki = GetWikiContent(new TypeAndID(RootObjectTypes.User, c.ID)); } //Custom fields? if (ShouldExportCustom) d.customFields = CustomFieldData(c, DateCustomFields); var a = await util.PostAsync("User", d.ToString()); long RavenId = util.IdFromResponse(a); Map.Add(c.ID, RavenId); //USER OPTIONS if (c.ScheduleBackColor != 0 || !string.IsNullOrWhiteSpace(c.EmailAddress)) { a = await util.GetAsync("UserOptions/" + RavenId.ToString()); d = a.ObjectResponse["data"]; d.uiColor = System.Drawing.ColorTranslator.ToHtml(System.Drawing.Color.FromArgb(c.ScheduleBackColor)); d.emailAddress = string.IsNullOrWhiteSpace(c.EmailAddress) ? null : c.EmailAddress; d.translationId = LocaleMap[c.DefaultLanguage]; await util.PutAsync("UserOptions/" + RavenId.ToString(), d.ToString()); } //Attachments / FILES await ExportAttachments(new TypeAndID(RootObjectTypes.User, c.ID), progress); } //EVENT LOG //Because this is the User's we need to do the event log *after* they have all been posted as event log requires all user's id foreach (UserPickList.UserPickListInfo i in pl) { if (!progress.KeepGoing) return; User c = User.GetItem(i.ID); var newId = Map[c.ID]; var creator = Map[c.Creator]; var modifier = Map[c.Modifier]; await util.EventLog(3, newId, creator, modifier, c.Created, c.Modified); } } #endregion clients #region locales private async System.Threading.Tasks.Task ExportLocales(ProgressForm progress) { progress.Append("Exporting customized Locales"); //checksum locales //if a recognized checksum then don't import because it means it wasn't modified from stock /* Stock fresh trial db (fb) values: Locale: Custom English hash code: 740187325 Locale: Deutsch hash code: -257973661 Locale: English hash code: 740187325 Locale: Español hash code: -373723947 Locale: Français hash code: -224208815 */ List StockLocaleHashes = new List(); StockLocaleHashes.Add(-257973661); StockLocaleHashes.Add(740187325); StockLocaleHashes.Add(-373723947); StockLocaleHashes.Add(-224208815); //Get a list of RAVEN translations and ID's var a = await util.GetAsync("Translation/List"); JArray ja = (JArray)a.ObjectResponse["data"]; var RavenLanguageList = ja.ToObject>(); LocaleList l = LocaleList.GetList(); foreach (LocaleList.LocaleListInfo i in l) { if (!progress.KeepGoing) return; LocalizedTextTable lt = LocalizedTextTable.Load(i.Locale); progress.Op("Checking if locale " + i.Locale + " is customized"); //calculate hash List allStrings = new List(); foreach (var entry in lt.LT) allStrings.Add(entry.Value); int CurrentLocaleHash = util.GetOrderIndependentHashCode(allStrings); allStrings.Clear(); if (StockLocaleHashes.Contains(CurrentLocaleHash)) continue; progress.Op("Locale " + i.Locale + " is customized"); //collection to hold items sent to server List exportItems = new List(); //Iterate all RAVEN languages for (int x = 1; x < 5; x++)//first four translations are the stock ones { //get raven name of locale var RavenLocaleName = RavenLanguageList.Find(m => m.Id == x).Name; //add stock locale mappings in case users are set to a stock one in v7 //so can set them later switch (RavenLocaleName) { case "en": LocaleMap.Add("English", x); break; case "fr": LocaleMap.Add("Français", x); break; case "de": LocaleMap.Add("Deutsch", x); break; case "es": LocaleMap.Add("Español", x); break; } //MAKE A DUPLICATE //name like this: "My custom (Espanol)" etc one for each target stock language var exportName = i.Locale + " (" + RavenLocaleName + ")"; progress.SubOp(""); progress.Op("Exporting " + i.Locale + " to " + exportName); var t = new util.NameIdItem { Name = exportName, Id = x }; a = await util.PostAsync("Translation/Duplicate", JObject.FromObject(t).ToString()); var targetTranslationId = util.IdFromResponse(a); var ctoken = util.CTokenFromResponse(a); //add to maps so can set user to it on export //going to default to the English based one because //that's the majority of the users if (RavenLocaleName == "en") LocaleMap.Add(i.Locale, targetTranslationId); //Ok, have our target locale created, not we need to insert our custom translations one by one var trans = ((JArray)a.ObjectResponse["data"]["translationItems"]).ToObject>(); //SET MATCHING KEYS TO OUR TEXT //iterate v7 locale items foreach (var v7item in lt.LT) { progress.SubOp("Processing key: " + v7item.Key); var v8key = Translatev7TranslationKey(v7item.Key); if (v8key == "**SKIP**") continue; System.Diagnostics.Debug.WriteLine("v7key:" + v7item.Key + " -> v8key: " + v8key); TranslationItem v8TransItem = trans.FirstOrDefault(m => m.Key == v8key); if (v8TransItem == null) { throw new ArgumentOutOfRangeException("On exporting custom locale " + i.Locale + " source key " + v7item.Key + ", destination key " + v8key + " was not found."); } //collect exportItems.Add(new UpdateTranslationItem { Id = v8TransItem.Id, ConcurrencyToken = v8TransItem.ConcurrencyToken, NewText = v7item.Value }); } //update it progress.SubOp("Posting translation " + exportName + " to server "); await util.PutAsync("Translation/UpdateTranslationItemsDisplayText", JArray.FromObject(exportItems).ToString()); } } progress.Op(""); progress.SubOp(""); } #region locale utility public class TranslationItem { public long Id { get; set; } public uint ConcurrencyToken { get; set; } public string Key { get; set; } public string Display { get; set; } public long TranslationId { get; set; } } public class UpdateTranslationItem { public long Id { get; set; } public uint ConcurrencyToken { get; set; } public string NewText { get; set; } } /// /// Used by import, translate the old v7 translation key name into the new shorter version /// /// /// public string Translatev7TranslationKey(string oldKey) { //skip keys definitely not in destination //swaths: if (oldKey.StartsWith("UI.ToolBar.")) return "**SKIP**"; if (oldKey.StartsWith("UI.Toolbar.")) return "**SKIP**"; // Custom English source key UI.Toolbar.ClientsToolBar, destination key ToolbarClientsToolBar was not found. switch (oldKey) { // case "XXXX": case "O.WikiPage": //skip over wikipage it's been dropped as it's an international term case "UI.Command.LocalizedTextDesign": case "O.WorkorderItemOutsideService": case "O.WorkorderService.CloseByDate": case "UI.Label.CurrentUserName": case "UI.Go.Search": return "**SKIP**"; } string s = oldKey.Replace(".Label.", "."); if (s.StartsWith("O.")) s = s.Replace("O.", ""); s = s.Replace(".ToolBar.", "."); s = s.Replace(".Go.", "."); s = s.Replace(".Command.", "."); s = s.Replace(".Error.", "."); s = s.Replace(".Object.", "."); if (s.StartsWith("UI.")) s = s.Replace("UI.", ""); s = s.Replace(".", ""); s = s.Replace("AddressAddress", "Address"); s = s.Replace("ContactPhoneContactPhone", "ContactPhone"); s = s.Replace("ContactPhonePhone", "ContactPhone"); s = s.Replace("PurchaseOrderPurchaseOrder", "PurchaseOrder"); s = s.Replace("WorkorderItemMiscExpenseExpense", "WorkorderItemMiscExpense"); s = s.Replace("WorkorderItemTravelTravel", "WorkorderItemTravel"); s = s.Replace("DashboardDashboard", "Dashboard"); //ScheduleMarkers -> Reminder s = s.Replace("ScheduleMarkerScheduleMarker", "ScheduleMarker"); s = s.Replace("ScheduleMarker", "Reminder"); s = s.Replace("ScheduleMarkerARGB", "ReminderARGB"); s = s.Replace("ScheduleMarkerColor", "ReminderColor"); s = s.Replace("ScheduleMarkerCompleted", "ReminderCompleted"); s = s.Replace("ScheduleMarkerEventCreated", "ReminderEventCreated"); s = s.Replace("ScheduleMarkerEventPendingAlert", "ReminderEventPendingAlert"); s = s.Replace("ScheduleMarkerFollowUp", "ReminderFollowUp"); s = s.Replace("ScheduleMarkerList", "ReminderList"); s = s.Replace("ScheduleMarkerName", "ReminderName"); s = s.Replace("ScheduleMarkerNotes", "ReminderNotes"); s = s.Replace("ScheduleMarkerRecurrence", "ReminderRecurrence"); s = s.Replace("ScheduleMarkerScheduleMarkerSourceType", "ReminderSourceType"); s = s.Replace("ScheduleMarkerSourceID", "ReminderSourceID"); s = s.Replace("ScheduleMarkerStartDate", "ReminderStartDate"); s = s.Replace("ScheduleMarkerStopDate", "ReminderStopDate"); s = s.Replace("ScheduleEditScheduleMarker", "ScheduleEditReminder"); s = s.Replace("ScheduleNewScheduleMarker", "ScheduleNewReminder"); //Custom fields were 0 to 9, now 1 to 16 s = s.Replace("Custom9", "Custom10"); s = s.Replace("Custom8", "Custom9"); s = s.Replace("Custom7", "Custom8"); s = s.Replace("Custom6", "Custom7"); s = s.Replace("Custom5", "Custom6"); s = s.Replace("Custom4", "Custom5"); s = s.Replace("Custom3", "Custom4"); s = s.Replace("Custom2", "Custom3"); if (!s.EndsWith("Custom10")) s = s.Replace("Custom1", "Custom2"); s = s.Replace("Custom0", "Custom1"); //separate code will handle adding the new keys that didn't exist in v7 (custom 11 - 16) //CommonActive CommonID etc remove Common s = s.Replace("Common", ""); //Misc s = s.Replace("FormFieldDataType", "UiFieldDataType"); s = s.Replace("UserUserType", "UserType"); s = s.Replace("UserTypesUtilityNotification", "UserTypesUtility"); //Localized -> Translation s = s.Replace("LocaleCustomizeText", "TranslationCustomizeText"); s = s.Replace("LocaleExport", "TranslationExport"); s = s.Replace("LocaleImport", "TranslationImport"); s = s.Replace("LocaleList", "TranslationList"); s = s.Replace("LocaleLocaleFile", "TranslationFile"); s = s.Replace("LocaleUIDestLocale", "TranslationDest"); s = s.Replace("LocaleUISourceLocale", "TranslationSource"); s = s.Replace("LocaleWarnLocaleLocked", "TranslationWarnLocked"); s = s.Replace("LocalizedTextDisplayText", "TranslationDisplayText"); s = s.Replace("LocalizedTextDisplayTextCustom", "TranslationDisplayTextCustom"); s = s.Replace("LocalizedTextKey", "TranslationKey"); s = s.Replace("LocalizedTextLocale", "Translation"); s = s.Replace("LocalizedText", "TranslatedText"); //items that came up after moving this to v8 export plugin s = s.Replace("DateRangeInTheLastYear", "DateRangePastYear"); s = s.Replace("ToolbarCustomizeDialog", "CustomizeDialog"); //FUTURE // s = s.Replace("WASXXX", "NOWXXXX"); // s = s.Replace("WASXXX", "NOWXXXX"); // s = s.Replace("WASXXX", "NOWXXXX"); // s = s.Replace("WASXXX", "NOWXXXX"); // s = s.Replace("WASXXX", "NOWXXXX"); // s = s.Replace("WASXXX", "NOWXXXX"); // s = s.Replace("WASXXX", "NOWXXXX"); // s = s.Replace("WASXXX", "NOWXXXX"); // s = s.Replace("WASXXX", "NOWXXXX"); return s; } #endregion locale utility #endregion locales #region TAG ITEMS #region Unitmodelcategories private void ExportUnitModelCategories(ProgressForm progress) { UnitModelCategories l = UnitModelCategories.GetItems(); progress.Append("Compiling " + l.Count.ToString() + " Unit model categories"); foreach (UnitModelCategory i in l) TagMap.Add(i.ID, util.NormalizeTag(i.Name + "." + "unitmodelcategory")); } #endregion #region Unitservicetypes private void ExportUnitServiceTypes(ProgressForm progress) { UnitServiceTypes l = UnitServiceTypes.GetItems(); progress.Append("Compiling " + l.Count.ToString() + " Unit service types"); foreach (UnitServiceType i in l) TagMap.Add(i.ID, util.NormalizeTag(i.Name + "." + "unitservicetype")); } #endregion #region WorkorderItemTypes private void ExportWorkorderItemTypes(ProgressForm progress) { WorkorderItemTypes l = WorkorderItemTypes.GetItems(); progress.Append("Compiling " + l.Count.ToString() + " Workorder item types"); foreach (WorkorderItemType i in l) TagMap.Add(i.ID, util.NormalizeTag(i.Name + "." + "workorderitemtype")); } #endregion #region REGIONS private void ExportRegions(ProgressForm progress) { RegionList l = RegionList.GetList(string.Empty); progress.Append("Compiling " + l.Count.ToString() + " Regions"); foreach (RegionList.RegionListInfo i in l) TagMap.Add(i.LT_Region_Label_Name.Value, util.NormalizeTag(i.LT_Region_Label_Name.Display + "." + "region")); } #endregion #region Client groups private void ExportClientGroups(ProgressForm progress) { ClientGroups l = ClientGroups.GetItems(); progress.Append("Compiling " + l.Count.ToString() + " Client groups"); foreach (ClientGroup i in l) TagMap.Add(i.ID, util.NormalizeTag(i.Name + "." + "clientgroup")); } #endregion #region Workorder categories private void ExportWorkorderCategories(ProgressForm progress) { WorkorderCategories l = WorkorderCategories.GetItems(); progress.Append("Compiling " + l.Count.ToString() + " Workorder categories"); foreach (WorkorderCategory i in l) TagMap.Add(i.ID, util.NormalizeTag(i.Name + "." + "workordercategory")); } #endregion #region Part categories private void ExportPartCategories(ProgressForm progress) { PartCategories l = PartCategories.GetItems(); progress.Append("Compiling " + l.Count.ToString() + " Part categories"); foreach (PartCategory i in l) TagMap.Add(i.ID, util.NormalizeTag(i.Name + "." + "partcategory")); } #endregion #region ScheduleableUserGroups private void ExportScheduleableUserGroups(ProgressForm progress) { ScheduleableUserGroupPickList pl = ScheduleableUserGroupPickList.GetList(); progress.Append("Compiling " + pl.Count.ToString() + " Scheduleable user groups"); foreach (ScheduleableUserGroupPickList.ScheduleableUserGroupPickListInfo i in pl) TagMap.Add(i.ID, util.NormalizeTag(i.Name + "." + "partcategory")); } #endregion clients #region Dispatch zones private void ExportDispatchZones(ProgressForm progress) { DispatchZones l = DispatchZones.GetItems(false); progress.Append("Compiling " + l.Count.ToString() + " Dispatch zones"); foreach (DispatchZone i in l) TagMap.Add(i.ID, util.NormalizeTag(i.Name + "." + "dispatchzone")); } #endregion private void ExportUserSkills(ProgressForm progress) { UserSkills l = UserSkills.GetItems(); progress.Append("Compiling " + l.Count.ToString() + " User skills"); foreach (UserSkill i in l) TagMap.Add(i.ID, util.NormalizeTag(i.Name + "." + "user-skill")); } private void ExportUserCertifications(ProgressForm progress) { UserCertifications l = UserCertifications.GetItems(); progress.Append("Compiling " + l.Count.ToString() + " User certifications"); foreach (UserCertification i in l) TagMap.Add(i.ID, util.NormalizeTag(i.Name + "." + "user-certification")); } #endregion TAG ITEMS //-------------------------------------------- #endregion object export #region Custom fields exporter //export objects custom field data into jobject string private string CustomFieldData(object biz, List dateFields) { dynamic d = new JObject(); for (int x = 0; x < 10; x++) { object o = (object)biz.GetType().GetProperty("Custom" + x.ToString()).GetValue(biz, null); string s = o.ToString(); if (string.IsNullOrWhiteSpace(s)) { s = null; } if (s != null && dateFields.Contains(x)) { //parse out to UTC date DateTime dt = new DateTime(); if (DateTime.TryParse(s, out dt)) { s = dt.ToUniversalTime().ToString("s"); } } if (!string.IsNullOrWhiteSpace(s)) d["c" + (x + 1).ToString()] = s; } return d.ToString(); } static public ObjectCustomFields ObjectHasCustomFieldDataToExport(string sObject) { ObjectCustomFields ocf = ObjectCustomFields.GetItems(sObject); if (ocf.Count == 0) { return null; } foreach (ObjectCustomField f in ocf) { if (f.Visible) return ocf; } return null; } private async System.Threading.Tasks.Task> ExportCustomFieldSchema(ObjectCustomFields ocf, string v7CustomFieldObjectName, string RavenCustomTranslationKeyObjectName) { var ret = new List(); //NOTE: this code inspired by winforApp::Util.cs PrepareCustomFieldsGrid method dynamic d = new JObject(); d.formkey = RavenCustomTranslationKeyObjectName; dynamic dtemplate = new JArray(); foreach (ObjectCustomField f in ocf) { if (f.Visible) { int n = Convert.ToInt32(f.FieldName.Replace("Custom", "")) + 1;//raven custom fields are 1 based, v7 are zero based //CustomFieldLocaleKeys.Add(RavenCustomTranslationKeyObjectName + n.ToString(), // util.LocaleText.GetLocalizedText(v7CustomFieldObjectName + ".Label." + f.FieldName)); dynamic dt = new JObject(); dt.fld = RavenCustomTranslationKeyObjectName + "Custom" + n.ToString(); dt.hide = false; dt.required = false; switch (f.FieldType) { case FormFieldDataTypes.Currency: dt.type = util.AyaUiFieldDataType.Currency; break; case FormFieldDataTypes.DateOnly: dt.type = util.AyaUiFieldDataType.Date; ret.Add(n - 1); break; case FormFieldDataTypes.DateTime: dt.type = util.AyaUiFieldDataType.DateTime; ret.Add(n - 1); break; case FormFieldDataTypes.Number: dt.type = util.AyaUiFieldDataType.Decimal; break; case FormFieldDataTypes.Text: dt.type = util.AyaUiFieldDataType.Text; break; case FormFieldDataTypes.TimeOnly: dt.type = util.AyaUiFieldDataType.Time; ret.Add(n - 1); break; case FormFieldDataTypes.TrueFalse: dt.type = util.AyaUiFieldDataType.Bool; break; default: dt.type = util.AyaUiFieldDataType.Text; break; } dtemplate.Add(dt); } } d.template = dtemplate.ToString(); //ok, were here because there *are* custom fields available var a = await util.GetAsync("FormCustom/" + RavenCustomTranslationKeyObjectName); var ctoken = util.CTokenFromResponse(a); d.concurrencyToken = ctoken; await util.PutAsync("FormCustom/" + RavenCustomTranslationKeyObjectName, d.ToString()); return ret; } #endregion custom fields #region Attachments exporter private async System.Threading.Tasks.Task ExportAttachments(TypeAndID tid, ProgressForm progress) { if (!WikiPage.HasWiki(tid.ID)) return; WikiPage w = WikiPage.GetItem(tid); AyaFileList fl = AyaFileList.GetList(w.ID); if (fl.Count == 0) return; //iterate the files foreach (AyaFileList.AyaFileListInfo i in fl) { if (!progress.KeepGoing) return; var af = AyaFile.GetItem(i.LT_O_AyaFile.Value); if (af == null) continue; progress.SubOp("Wikifile: \"" + af.Name + "\" " + AyaBizUtils.FileSizeDisplay((decimal)af.FileSize)); //Compile the FileData property var sDate = i.LT_Common_Label_Created.ToString(); DateTimeOffset dtLastModified = DateTime.UtcNow; if (sDate != null) { //parse out to UTC date DateTime dt = new DateTime(); if (DateTime.TryParse(sDate, out dt)) { dtLastModified = dt.ToUniversalTime(); } } dynamic dFileData = new JArray(); dynamic dFile = new JObject(); dFile.name = af.Name; dFile.lastModified = dtLastModified.ToUnixTimeMilliseconds(); dFileData.Add(dFile); //Upload MultipartFormDataContent formDataContent = new MultipartFormDataContent(); //Form data like the bizobject type and id formDataContent.Add(new StringContent(util.RootObjectToAyaType(tid.RootObjectType).ToString()), name: "AttachToObjectType"); formDataContent.Add(new StringContent(Map[tid.ID].ToString()), name: "AttachToObjectId"); formDataContent.Add(new StringContent(ImportTag), name: "Notes"); formDataContent.Add(new StringContent(dFileData.ToString()), name: "FileData"); StreamContent AttachmentFile = new StreamContent(af.GetContent()); AttachmentFile.Headers.ContentType = new MediaTypeHeaderValue(af.mimeType); AttachmentFile.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data"); AttachmentFile.Headers.ContentDisposition.FileName = af.Name; AttachmentFile.Headers.ContentDisposition.ModificationDate = dtLastModified; formDataContent.Add(AttachmentFile); //Upload var a = await util.PostFormDataAsync("Attachment", formDataContent); //Map it for later processing of wiki var ravenId = a.ObjectResponse["data"][0]["id"].Value(); Map.Add(af.ID, ravenId); } progress.SubOp(""); } #endregion attachments #region WIKI page exporter //WIKI #region Wikiable objects reference /* Find all "Util.OpenWikiPage", Whole word, Subfolders, Keep modified files open, Find Results 1, Entire Solution, "" C:\data\ayanova\source\WinFormApp\ClientInfoForm.cs(1579): Util.OpenWikiPage(RootObjectTypes.Client, mClient.ID,false); C:\data\ayanova\source\WinFormApp\ContractInfoForm.cs(687): Util.OpenWikiPage(RootObjectTypes.Contract, mContract.ID, false); C:\data\ayanova\source\WinFormApp\Form1.cs(3713): Util.OpenWikiPage(RootObjectTypes.Global, Address.GlobalAddressID, false); C:\data\ayanova\source\WinFormApp\Form1.cs(3856): Util.OpenWikiPage(RootObjectTypes.User, User.CurrentThreadUserID, false); C:\data\ayanova\source\WinFormApp\Form1.cs(3867): // Util.OpenWikiPage(RootObjectTypes.User,User.CurrentThreadUserID,false); C:\data\ayanova\source\WinFormApp\HeadOfficeInfoForm.cs(1402): Util.OpenWikiPage(RootObjectTypes.HeadOffice, mHeadOffice.ID,false); C:\data\ayanova\source\WinFormApp\LoanItemInfoForm.cs(860): Util.OpenWikiPage(RootObjectTypes.LoanItem, mLoanItem.ID, false); C:\data\ayanova\source\WinFormApp\PartInfoForm.cs(1111): Util.OpenWikiPage(RootObjectTypes.Part, mPart.ID, false); C:\data\ayanova\source\WinFormApp\ProjectInfoForm.cs(712): Util.OpenWikiPage(RootObjectTypes.Project, mProject.ID, false); C:\data\ayanova\source\WinFormApp\PurchaseOrderInfoForm.cs(1010): Util.OpenWikiPage(RootObjectTypes.PurchaseOrder, mPurchaseOrder.ID, false); C:\data\ayanova\source\WinFormApp\RegionInfoForm.cs(1186): Util.OpenWikiPage(RootObjectTypes.Region, mRegion.ID, false); C:\data\ayanova\source\WinFormApp\UnitInfoForm.cs(1280): Util.OpenWikiPage(RootObjectTypes.Unit, mUnit.ID, false); C:\data\ayanova\source\WinFormApp\UnitModelInfoForm.cs(926): Util.OpenWikiPage(RootObjectTypes.UnitModel, mUnitModel.ID, false); C:\data\ayanova\source\WinFormApp\UserInfoForm.cs(1395): Util.OpenWikiPage(RootObjectTypes.User, mUser.ID, false); C:\data\ayanova\source\WinFormApp\VendorInfoForm.cs(1193): Util.OpenWikiPage(RootObjectTypes.Vendor, mVendor.ID, false); C:\data\ayanova\source\WinFormApp\WorkorderForm.cs(10332): Util.OpenWikiPage(mWorkorder.RootObjectType, mWorkorder.ID, false);//case 1584 was RootObjectTypes.Workorder C:\data\ayanova\source\WBI\maingrid.aspx.cs(1277): Util.OpenWikiPage(this.Page, new TypeAndID(RootObjectTypes.Global, Address.GlobalAddressID)); C:\data\ayanova\source\WBI\schedule.aspx.cs(187): Util.OpenWikiPage(this.Page, new TypeAndID(RootObjectTypes.Global, Address.GlobalAddressID)); Matching lines: 18 Matching files: 16 Total files searched: 1769 */ #endregion private string GetWikiContent(TypeAndID tid) { //may not exist // if (!WikiPage.HasWiki(tid.ID)) return null; WikiPage w = WikiPage.GetItem(tid); var content = w.GetContentAsString; if (string.IsNullOrWhiteSpace(content)) return null; //TODO: fixup internal urls using MAP of file attachment uploads MatchCollection mc = AyaBizUtils.rxAyaImageTags.Matches(content); foreach (Match m in mc) { var RavenId = Map[new Guid(m.Groups["guid"].Value)]; content = content.Replace(m.Value, ""); } //todo: fixup font size, try to convert to heading tags instead //figure out how to remove the inline style tag as it comes through for some reason //TODO: Convert to Markdown format //found 2 likely candidate libs to do this //https://www.nuget.org/packages/Html2Markdown/ //https://www.nuget.org/packages/ReverseMarkdown/ //Not much difference at first glance, very similar stats, reverse markdown is a little more widely used and seems to have more config options //built in, the other has to make some kind of processor for some stuff maybe so going with reverse for now //NOTE: for this to work had to add an assembly redirect in app.config for ayanova project as it was looking for a really old version of //the HtmlAgilityPack var config = new ReverseMarkdown.Config { UnknownTags = ReverseMarkdown.Config.UnknownTagsOption.Bypass, GithubFlavored = true, // generate GitHub flavoured markdown, supported for BR, PRE and table tags RemoveComments = true, // will ignore all comments, (narrator: "It doesn't") SmartHrefHandling = true // remove markdown output for links where appropriate }; var converter = new ReverseMarkdown.Converter(config); string res = converter.Convert(content); //strip out comment and style chunk that is left behind by above tool //style is an artifact of the rtf2html converter used by v7 int nStart = res.IndexOf(""); if (nStart != -1 && nEnd != -1) { res = res.Substring(0, nStart) + res.Substring(nEnd + 3); // res = newRes; } return res; } #region sample before and after /* * INPUT * Untitled document
Here is a title
 
This document has all possible options on it except for picking every single font size so instead here is the smallest font size which is 6
and here is the largest font size which is 150
 
italics and underlined and bold
 
red
 
green
 
blue
 
  • ·      List one
  • ·      list two
  • ·      list three
 
 
 
Arial font
 
Consolas font
 
Comic  sans
 
font size 16 double default size whih is 8
 
font size 32 which is quadruple default of 8
 
 
This block is
left aligned
 
 
this block is
center aligned
 
This block is
right aligned
 
 
 
 Wiki page - User: AyaNova Administrator
 
and more:
 
and more
 
 
======================================== Untitled document
Wiki page - User: Eva Alexander - ALL REGIONS
Here is some under title text
 
Here is another title
 
Here is some more under title text.
Some more lines
like this one
and this one
 
=-=-=-==- * * * output * Untitled document **Here is a title** This document has all possible options on it except for picking every single font size so instead here is the smallest font size which is 6 and here is the largest font size which is 150 *italics*and underlined and **bold** **red** **green** **blue** - **·****List one** - **·****list two** - **·****list three** Arial font Consolas font Comic  sans font size 16 double default size whih is 8 font size 32 which is quadruple default of 8 This block is left aligned this block is center aligned This block is right aligned ![]([ATTACH:4])Wiki page - User: AyaNova Administrator and more: ![]([ATTACH:4]) and more ![]([ATTACH:4]) ========================= Untitled document Wiki page - User: Eva Alexander - ALL REGIONS Here is some under title text Here is another title Here is some more under title text. Some more lines like this one and this one * */ #endregion sample #endregion wiki #region TAGS private void Tagit(Guid g, List tags) { if (g == Guid.Empty) return; if (!TagMap.ContainsKey(g)) return; var t = TagMap[g]; if (!string.IsNullOrWhiteSpace(t)) { tags.Add(t); } } private void SetTags(dynamic d, List tags) { dynamic dtags = new JArray(); foreach (string s in tags) dtags.Add(s); d.tags = dtags; } #endregion tags #region OLD JSON EXPORT STUFF #region Global settings private void ExportGlobalSettings(ProgressForm progress) { progress.Append("STUB: Dumping Global Settings"); ////DumpObjectToFolder(tempArchiveFolder, AyaBizUtils.GlobalSettings, "globalsettings", objectExcludeProperties, new TypeAndID(RootObjectTypes.Global, Address.GlobalAddressID)); } #endregion globalsettings #region Seeds private class GZSeeds { public int InventoryAdjustmentStartSeed = 1; public int PurchaseOrderStartSeed = 1; public int QuoteNumberStartSeed = 1; public int WorkorderNumberStartSeed = 1; public int PreventiveMaintenanceNumberStartSeed = 1; } private void ExportSeedNumbers(ProgressForm progress) { // List objectExcludeProperties = new List(standardExcludePropertiesList); progress.Append("Dumping seeds"); //create a new object with the id numbers in it and then dump it WorkorderPMList pml = WorkorderPMList.GetList(" \r\n" + " \r\n" + " \r\n" + " "); int PMStartSeed = 0; if (pml.Count > 0) { PMStartSeed = int.Parse(pml[0].LT_O_WorkorderPreventiveMaintenance.Display); } var seeds = new GZSeeds(); seeds.InventoryAdjustmentStartSeed = AyaBizUtils.GlobalSettings.InventoryAdjustmentStartSeed + 1; seeds.PurchaseOrderStartSeed = AyaBizUtils.GlobalSettings.PurchaseOrderStartSeed + 1; seeds.QuoteNumberStartSeed = AyaBizUtils.GlobalSettings.QuoteNumberStartSeed + 1; seeds.WorkorderNumberStartSeed = AyaBizUtils.GlobalSettings.WorkorderNumberStartSeed + 1; seeds.PreventiveMaintenanceNumberStartSeed = PMStartSeed + 1; //DumpObjectToFolder(tempArchiveFolder, seeds, "seeds", objectExcludeProperties, TypeAndID.Empty, "GZTW.AyaNova.BLL.Seed"); } #endregion globalsettings #region clients private void ExportClients(ProgressForm progress) { ClientPickList pl = ClientPickList.GetList(); progress.Append("Dumping " + pl.Count.ToString() + " Clients"); foreach (ClientPickList.ClientPickListInfo i in pl) { Client c = Client.GetItem(i.ID); //DumpObjectToFolder(tempArchiveFolder, c, "client." + c.ID.ToString(), objectExcludeProperties, new TypeAndID(RootObjectTypes.Client, c.ID)); } } #endregion clients #region headoffices private void ExportHeadOffices(ProgressForm progress) { PickListAutoComplete pl = PickListAutoComplete.GetList("**", "headoffice"); progress.Append("Dumping " + pl.Count.ToString() + " Head offices"); foreach (PickListAutoComplete.PickListAutoCompleteInfo i in pl) { HeadOffice c = HeadOffice.GetItem(i.ID); //DumpObjectToFolder(tempArchiveFolder, c, "headoffice." + c.ID.ToString(), excludes, new TypeAndID(RootObjectTypes.HeadOffice, c.ID)); } } #endregion clients #region contract resolver //public class ExcludeNamedPropertiesContractResolver : DefaultContractResolver //{ // private readonly List _excludeProperties; // public ExcludeNamedPropertiesContractResolver(List excludeProperties) // { // _excludeProperties = excludeProperties; // } // protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) // { // IList properties = base.CreateProperties(type, memberSerialization); // // only serializer properties that start with the specified character // //properties = properties.Where(p => p.PropertyName.StartsWith(_startingWithChar.ToString())).ToList(); // properties = properties.Where(p => !_excludeProperties.Contains(p.PropertyName)).ToList(); // return properties; // } //} #endregion contract resolver //private static string EnsureValidFileName(string fileName) //{ // //make lower and replace spaces with dashes // fileName = fileName.ToLowerInvariant().Replace(" ", "-"); // //ensure each character is a valid path character // foreach (char c in System.IO.Path.GetInvalidFileNameChars()) // { // fileName = fileName.Replace(c, '_'); // } // return fileName; //} //private static void makeFolderIfNotExist(string fldr, bool shouldNotExist = false) //{ // if (Directory.Exists(fldr)) // { // if (shouldNotExist) // throw new System.Exception("Error: path already exists and shouldn't:\r\n" + fldr); // return; // } // Directory.CreateDirectory(fldr); //} //private List standardExcludePropertiesList //{ // get // { // return new List() // { // "CanWiki", // "CanDuplicate", // "IsValid", // "IsDirty", // "CurrentUserID", // "IsEditing", // "IsNew", // "IsDeleted", // "IsSavable", // "Notify", // "BrokenRulesText", // "Docs", // "MapQuestURL", // "FullAddress" // //"XXX", // //"XXX", // //"XXX", // //"XXX", // //"XXX", // //"XXX", // //"XXX", // //"XXX", // //"XXX", // //"XXX", // //"XXX" // }; // } //} #endregion old //eoc } //eons }