using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.Dbas; using Sarand.Net.Standard.Base.Helpers; using Sarand.Net.Standard.Base.Utils; using Saxonite.Net.Core.Web.Online.App; using Saxonite.Net.Core.Web.Online.Models; using Saxonite.Net.Standard.Base.App; using Saxonite.Net.Standard.Base.Bus; using Saxonite.Net.Standard.Base.Consoles; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Linq; using System.Threading.Tasks; namespace Saxonite.Net.Core.Web.Online.Controllers { //======================================================================= // Online Journalist Controller - Ajax actions. //======================================================================= partial class JournalistController { //------------------------------------------------------------------- // Journalist Article Content data. public class ArticleContentData { public List Segments { get; } = new List(); } public class ArticleContentSegmentData { public ArticleContentSegmentBodyData Body { get; set; } public ArticleContentSegmentImageData Image { get; set; } } public class ArticleContentSegmentBodyData { public String Html { get; set; } } public class ArticleContentSegmentImageData { public int ItemImageId { get; set; } public int ImageIdx { get; set; } public String ImageLink { get; set; } } //------------------------------------------------------------------- // Journalist Article data. public class ArticleData_List { public String Id { get; set; } public String Headline { get; set; } public String Summary { get; set; } public String RegionCaptionPrimary { get; set; } public String SportCaptionPrimary { get; set; } public String PublishedOnFmt { get; set; } public String CreatedOnFmt { get; set; } public String StatusCode { get; set; } public String StatusDesc { get; set; } } public class ArticleData_Full : ArticleData_List { public String RegionIdPrimary { get; set; } public String SportIdPrimary { get; set; } public String PublishedOn_YYYYMMDD { get; set; } public ArticleContentData Content { get; } = new ArticleContentData(); } private void PopulateArticleResponseData(Model_Journalist model, SaxJournalistArticle article, ArticleData_List data) { data.Id = SysId.ToString(article.Id); data.Headline = article.Headline; data.Summary = article.Summary; data.RegionCaptionPrimary = model.GetRegionCaption(article.RegionIdPrimary); data.SportCaptionPrimary = model.GetSportCaption(article.SportIdPrimary); data.CreatedOnFmt = DateUtil.Format(article.CreateStamp.On, "d-MMM-yyyy"); data.PublishedOnFmt = DateUtil.Format(article.PublishedAt, "d-MMM-yyyy"); data.StatusCode = article.StatusCode; data.StatusDesc = article.StatusDesc; } private void PopulateArticleResponseData(Model_Journalist model, SaxJournalistArticle article, ArticleData_Full data) { PopulateArticleResponseData(model, article, data as ArticleData_List); data.RegionIdPrimary = SysId.ToString(article.RegionIdPrimary); data.SportIdPrimary = SysId.ToString(article.SportIdPrimary); data.PublishedOn_YYYYMMDD = DateUtil.Format(article.PublishedAt, "yyyyMMdd"); // Article content. SaxJournalistArticle.ContentData articleContent = SaxJournalistArticle.ExtractContent(article.BodyData); foreach (var contentSegment in articleContent.Segments) { var segmentData = new ArticleContentSegmentData(); if (contentSegment.Body.IsValid()) { segmentData.Body = new ArticleContentSegmentBodyData(); segmentData.Body.Html = contentSegment.Body.Html; } else if (contentSegment.Image.IsValid()) { segmentData.Image = new ArticleContentSegmentImageData(); segmentData.Image.ItemImageId = SysId.ToInt(contentSegment.Image.ItemImageId); segmentData.Image.ImageLink = GetArticleImageLink(contentSegment.Image.ItemImageId); } data.Content.Segments.Add(segmentData); } } //------------------------------------------------------------------- // Get Article List ajax request. //------------------------------------------------------------------- public class AjaxResponse_ArticleList : AjaxResponse_Data { public List Articles { get; } = new List(); } //------------------------------------------------------------------- [HttpPost] [Route("Journalist/Ajax/GetArticleList")] public IActionResult Ajax_GetArticleList(Model_Journalist model) { String fn = GetType().Name + ".Ajax_GetArticleList"; String messageTitle = Localiser["journalist.caption.articles"]; AjaxResponse_ArticleList responseData = new AjaxResponse_ArticleList(); // Validate session and referrer. if (!ValidReferer) return Forbid(); if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); if (!UserIsJournalist) return GetAjaxResult_GoHome(responseData); SaxSessionData_Online sessionData = SessionData_Online; // Item filter. SaxJournalistArticle.Params prmArticle = new SaxJournalistArticle.Params(); prmArticle.UserAccountIdsJournalistIncluded.Add(sessionData.User.UserAccountId); prmArticle.ArticleFilter = model.Filter.Article; prmArticle.StatusCat = SaxJournalistArticle.StatusCat.NotDeleted; model.DataItemTotal = (ulong)ActivityMgr.GetJournalistArticleCount(prmArticle); model.PopulatePagingTo(prmArticle.ResultsPaging); // Item columns. ColumnDfn colId = ActivityMgr.GetJournalistArticleColumnDfn(SaxJournalistArticle.Column.Id); ColumnDfn colHeadline = ActivityMgr.GetJournalistArticleColumnDfn(SaxJournalistArticle.Column.Headline); ColumnDfn colSummary = ActivityMgr.GetJournalistArticleColumnDfn(SaxJournalistArticle.Column.Summary); ColumnDfn colRegionIdPrimary = ActivityMgr.GetJournalistArticleColumnDfn(SaxJournalistArticle.Column.RegionIdPrimary); ColumnDfn colSportIdPrimary = ActivityMgr.GetJournalistArticleColumnDfn(SaxJournalistArticle.Column.SportIdPrimary); ColumnDfn colPublishedOn = ActivityMgr.GetJournalistArticleColumnDfn(SaxJournalistArticle.Column.PublishedAt_Date); ColumnDfn colStatus = ActivityMgr.GetJournalistArticleColumnDfn(SaxJournalistArticle.Column.Status); ColumnDfn colCreatedOn = ActivityMgr.GetJournalistArticleColumnDfn(SaxJournalistArticle.Column.CreateOn_Date); List columns = new List() { colId, colHeadline, colSummary, colRegionIdPrimary, colSportIdPrimary, colPublishedOn, colStatus, colCreatedOn }; // Sort columns. colCreatedOn.SortAscending = false; prmArticle.SortColumns.Add(colCreatedOn); // Query items. model.DataItemCount = 0; using (DbResultSet resultSet = ActivityMgr.ExecuteJournalistArticleQuery(columns, prmArticle)) { while (resultSet != null && resultSet.HasRows && resultSet.NextRecord()) { model.DataItemCount++; NamedValue.Map values = resultSet.GetRecordValues(); ArticleData_List articleData = new ArticleData_List() { Id = values.GetString(colId.DataName), Headline = values.GetString(colHeadline.DataName), Summary = values.GetString(colSummary.DataName), RegionCaptionPrimary = model.GetRegionCaption(values.GetString(colRegionIdPrimary.DataName).ToSysId()), SportCaptionPrimary = model.GetSportCaption(values.GetString(colSportIdPrimary.DataName).ToSysId()), CreatedOnFmt = DateUtil.Format(values.GetDateTime(colCreatedOn.DataName), "d-MMM-yyyy"), PublishedOnFmt = DateUtil.Format(values.GetDateTime(colPublishedOn.DataName), "d-MMM-yyyy"), StatusCode = values.GetString(colStatus.DataName), StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName)) }; responseData.Articles.Add(articleData); } } // Complete response. responseData.SetPaging(model); responseData.Success = true; return Json(responseData); } //------------------------------------------------------------------- // Get Article ajax request. //------------------------------------------------------------------- public class AjaxResponse_Article : AjaxResponse_Data { public ArticleData_Full Article { get; } = new ArticleData_Full(); } //------------------------------------------------------------------- [HttpPost] [Route("Journalist/Ajax/GetArticle")] public IActionResult Ajax_GetArticle(Model_Journalist model) { String fn = GetType().Name + ".Ajax_GetArticle"; String messageTitle = Localiser["journalist.caption.article"]; AjaxResponse_Article responseData = new AjaxResponse_Article(); // Validate session and referrer. if (!ValidReferer) return Forbid(); if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); if (!UserIsJournalist) return GetAjaxResult_GoHome(responseData); // Identify item. SysId articleId = model.ArticleId.ToSysId(); if (!SysId.IsValid(articleId)) return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"])); // Retrieve item. SaxJournalistArticle article = ActivityMgr.GetJournalistArticleById(articleId); if (article == null) { SaLogger.Warn(fn + " Failed to retrieve Journalist Article for Id: '" + model.ArticleId + "'"); return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); } // Delete upload images. SaxItemImage.Params prmItemImage = new SaxItemImage.Params(); prmItemImage.ItemIdsIncluded.Add(new ItemId((short)Sax.DataItemType.JournalistArticle, articleId)); prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.ArticleUpload); prmItemImage.StatusCat = SaxItemImage.StatusCat.NotDeleted; foreach (SaxItemImage itemImage in ItemDataMgr.GetItemImageList(prmItemImage)) { itemImage.SessionUid = 0; itemImage.ImageUid = 0; itemImage.Status = SaStatus.Deleted; ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo); } // Construct response. PopulateArticleResponseData(model, article, responseData.Article); responseData.Success = true; return Json(responseData); } //------------------------------------------------------------------- // Journalist - Upload Article Image. //------------------------------------------------------------------- public class AjaxResponse_UploadArticleImage : AjaxResponse_Data { public ArticleContentSegmentData Segment { get; set; } public String UploadMsg { get; set; } } // Ajax request. [HttpPost] [Route("Journalist/Ajax/UploadArticleImage")] public IActionResult UploadArticleImage(Model_Journalist model) { String fn = GetType().Name + ".UploadArticleImage"; AjaxResponse_UploadArticleImage responseData = new AjaxResponse_UploadArticleImage(); String messageTitle = Localiser["journalist.caption.article"]; // Validate session and referrer. if (!ValidReferer) return Forbid(); if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); SaxSessionData_Online sessionData = SessionData_Online; // Validate number of images. if (model.ArticleImage.IsEmpty()) { SaLogger.Warn(fn + " No image file supplied for upload"); return Json(responseData); } if (model.ArticleImage.Count != 1) { SaLogger.Warn(fn + " Invalid number of image files uploaded: " + model.ArticleImage.Count); return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.single"])); } IFormFile file = model.ArticleImage[0]; // Image properties. byte[] imageData = null; using (MemoryStream fileStream = new MemoryStream()) { file.CopyTo(fileStream); imageData = fileStream.ToArray(); } // Validate the image. ImageUtils.ImageConfig imageConfig = ImageUtils.VerifyImageData(imageData); if (!imageConfig.ValidImage) return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.invalid"])); // Image belongs to the article. // If the article doesn't yet exist, it will need to be uploaded to the user first and later transferred. SysId articleId = model.ArticleId.ToSysId(); ItemId articleItemId = new ItemId((short)Sax.DataItemType.JournalistArticle, articleId); ItemId userItemId = new ItemId((short)Sax.DataItemType.UserAccount, sessionData.User.UserAccountId); ItemId imageItemId = SysId.IsValid(articleId) ? articleItemId : userItemId; // Store uploaded image as 'Upload' - awaiting confirmation that the article details are to be saved. // NB Reuse any existing deleted article upload image to avoid creating new images unless necessary. SaxItemImage.Params prmItemImage = new SaxItemImage.Params(); prmItemImage.ItemIdsIncluded.Add(imageItemId); prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Article); prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.ArticleUpload); prmItemImage.StatusCat = SaxItemImage.StatusCat.Deleted; prmItemImage.ResultsPaging.PageLength = 1; prmItemImage.ResultsPaging.PageNo = 1; List itemImages = ItemDataMgr.GetItemImageList(prmItemImage); SaxItemImage itemImage = !itemImages.IsEmpty() ? itemImages[0] : null; if (itemImage == null) itemImage = ItemDataMgr.CreateItemImage(imageItemId, Sax.ImageType.ArticleUpload); itemImage.ImageType = Sax.ImageType.ArticleUpload; itemImage.Filename = file.FileName; itemImage.Mime = SacsContentTypeHelper.GetMimeTypeFromContentType(file.ContentType).ToString().ToLower(); itemImage.ContentType = file.ContentType; itemImage.Width = imageConfig.Width; itemImage.Height = imageConfig.Height; itemImage.ImageData = imageData; itemImage.SessionUid = sessionData.SessionIdx; itemImage.ImageUid = sessionData.NextItemIdx; itemImage.Status = SaxItemImage.DefaultStatus; if (!SysId.IsValid(itemImage.Id)) { if (!ItemDataMgr.InsertItemImage(itemImage, DbUpdateInfo)) { SaLogger.Warn(fn + " Failed to insert uploaded image - " + ItemDataMgr.ErrorStateMessage); return Json(responseData.SetControlError("ArticleImage", Localiser["msg.error.item.insert.failed"])); } } else { if (!ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo)) { SaLogger.Warn(fn + " Failed to update uploaded image - " + ItemDataMgr.ErrorStateMessage); return Json(responseData.SetControlError("ArticleImage", Localiser["msg.error.item.insert.failed"])); } } // Segment image info. responseData.Segment = new ArticleContentSegmentData(); responseData.Segment.Image = new ArticleContentSegmentImageData(); responseData.Segment.Image.ImageLink = GetArticleImageLink(itemImage.Id); responseData.Segment.Image.ImageIdx = itemImage.ImageUid; // Complete response. //responseData.SetNotification(messageTitle, "Image uploaded."); responseData.Success = true; return Json(responseData); } private String GetArticleImageLink(SysId itemImageId) { return GetArticleImageLink(this, itemImageId); } public static String GetArticleImageLink(SaxController_Online controller, SysId itemImageId) { String imageLink = "Image/Journalist/Article?"; NameValueCollection parameters = new NameValueCollection(); parameters.Add(ImageController.ParamName_ItemImageId, SysId.ToString(itemImageId)); return imageLink + ImageController.ParamName_JournalistArticleImageEncrypted + "=" + SaxController_Online.EncryptOnlineParams(controller, parameters); } //------------------------------------------------------------------- // Store Article ajax request. //------------------------------------------------------------------- [HttpPost] [Route("Journalist/Ajax/StoreArticle")] public IActionResult Ajax_StoreArticle(Model_Journalist model) { String fn = GetType().Name + ".Ajax_StoreArticle"; String messageTitle = Localiser["journalist.caption.article"]; AjaxResponse_Article responseData = new AjaxResponse_Article(); // Validate session and referrer. if (!ValidReferer) return Forbid(); if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); if (!UserIsJournalist) return GetAjaxResult_GoHome(responseData); SaxSessionData_Online sessionData = SessionData_Online; // Validate item properties. model.ValidateArticleData(responseData); if (responseData.HasControlErrors()) return Json(responseData); // Validate Article against Id against 'Exists' flag. SysId articleId = model.ArticleId.ToSysId(); bool articleExists = StringUtil.ToBool(model.ArticleExists); if (articleExists && !SysId.IsValid(articleId)) { SaLogger.Warn(fn + " Id not specified for existing Article."); return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"])); } if (!articleExists && SysId.IsValid(articleId)) { SaLogger.Warn(fn + " Id specified for new Article: '" + model.ArticleId + "'"); return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"])); } // Retrieve existing item. SaxJournalistArticle article = ActivityMgr.GetJournalistArticleById(articleId); if (article == null && SysId.IsValid(articleId)) { SaLogger.Warn(fn + " Failed to retrieve article for Id: '" + model.ArticleId + "'"); return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); } // Create new item if required. if (article == null) article = ActivityMgr.CreateJournalistArticle(sessionData.User.UserAccountId); // Populate item properties. if (model.ArticleRegionId != null) article.RegionIdPrimary = model.ArticleRegionId.ToSysId(); if (model.ArticleSportId != null) article.SportIdPrimary = model.ArticleSportId.ToSysId(); if (model.ArticleHeadline != null) article.Headline = model.ArticleHeadline; if (model.ArticleSummary != null) article.Summary = model.ArticleSummary; if (model.ArticleStatusCode != null) article.Status = model.ArticleStatus; // Retrieve all article images - before new article is created as uploaded images are assigned to user first. SaxItemImage.Params prmItemImage = new SaxItemImage.Params(); if (SysId.IsValid(article.Id)) prmItemImage.ItemIdsIncluded.Add(article.GetItemId()); else prmItemImage.ItemIdsIncluded.Add(new ItemId((short)Sax.DataItemType.UserAccount, sessionData.User.UserAccountId)); prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Article); prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.ArticleUpload); prmItemImage.StatusCat = SaxItemImage.StatusCat.NotDeleted; List itemImages = ItemDataMgr.GetItemImageList(prmItemImage); // If Article doesn't yet exist, create it now so that images can be assigned. if (!SysId.IsValid(article.Id)) { if (!ActivityMgr.InsertJournalistArticle(article, DbUpdateInfo)) { SaLogger.Warn(fn + " Failed to insert article: " + ActivityMgr.ErrorStateMessage); return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ActivityMgr.ErrorStateMessage)); } } // Content. if (model.ArticleContentSegmentBodies != null) { SaxJournalistArticle.ContentData articleContent = new SaxJournalistArticle.ContentData(); for (int segmentIdx = 0; segmentIdx < model.ArticleContentSegmentBodies.Count; segmentIdx++) { String segmentBody = model.ArticleContentSegmentBodies[segmentIdx]; String segmentItemImageId = model.ArticleContentSegmentItemImageIds.Count > segmentIdx ? model.ArticleContentSegmentItemImageIds[segmentIdx] : ""; String segmentImageIdx = model.ArticleContentSegmentImageIdxs.Count > segmentIdx ? model.ArticleContentSegmentImageIdxs[segmentIdx] : ""; if (!StringUtil.IsEmpty(segmentBody)) { // HTML Body segment. var segment = articleContent.AddSegment(); segment.Body.Html = segmentBody; } else if (!StringUtil.IsEmpty(segmentItemImageId) || !StringUtil.IsEmpty(segmentImageIdx)) { SysId itemImageId = segmentItemImageId.ToSysId(); int imageIdx = StringUtil.ToInt(segmentImageIdx); // Check for new image first. SaxItemImage itemImage = itemImages.GetItemImageBySessionImage(sessionData.SessionIdx, imageIdx); if (itemImage == null) itemImage = itemImages.GetItemImageById(itemImageId); if (itemImage != null) { // Ensure that the image is correctly identified for the Article. itemImage.ItemId = article.GetItemId(); itemImage.ImageType = Sax.ImageType.Article; itemImage.SessionUid = 0; itemImage.ImageUid = 0; itemImage.Status = SaxItemImage.DefaultStatus; if (ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo)) { // Assign the image segment to the article. var segment = articleContent.AddSegment(); segment.Image.ItemImageId = itemImage.Id; // Remove the image from the list as it's now been handled. itemImages.Remove(itemImage); } else SaLogger.Warn(fn + " Failed to update article image: " + ItemDataMgr.ErrorStateMessage); } else SaLogger.Warn(fn + " Failed to identify article image. ItemImageId: " + itemImageId.ToInt() + ". SessionIdx: " + sessionData.SessionIdx + ". ImageIdx: " + imageIdx + "."); } } article.BodyData = SaxJournalistArticle.GetContentData(articleContent); } // Update the Article. if (!ActivityMgr.UpdateJournalistArticle(article, DbUpdateInfo)) { SaLogger.Warn(fn + " Failed to update article [Id:" + SysId.ToString(article.Id) + "]: " + ActivityMgr.ErrorStateMessage); return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ActivityMgr.ErrorStateMessage)); } // Store Article Activity Index data. ActivityConsole.StoreActivityIndex(article, DbUpdateInfo); // Delete remaining images as they are no longer required. foreach (SaxItemImage itemImage in itemImages) { itemImage.SessionUid = 0; itemImage.ImageUid = 0; itemImage.Status = SaStatus.Deleted; ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo); } // Construct response. PopulateArticleResponseData(model, article, responseData.Article); responseData.Success = true; return Json(responseData); } //------------------------------------------------------------------- } //======================================================================= }