773 lines
34 KiB
C#
773 lines
34 KiB
C#
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 Saxonite.Net.Standard.Base.Mgr;
|
|
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 Article Controller - Ajax actions.
|
|
//=======================================================================
|
|
partial class ArticleController
|
|
{
|
|
//-------------------------------------------------------------------
|
|
// Article Content data.
|
|
private class ArticleContentData
|
|
{
|
|
public List<ArticleContentSegmentData> Segments { get; } = new List<ArticleContentSegmentData>();
|
|
}
|
|
private class ArticleContentSegmentData
|
|
{
|
|
public ArticleContentSegmentBodyData Body { get; set; }
|
|
public ArticleContentSegmentImageData Image { get; set; }
|
|
}
|
|
private class ArticleContentSegmentBodyData
|
|
{
|
|
public String Html { get; set; }
|
|
}
|
|
private class ArticleContentSegmentImageData
|
|
{
|
|
public int ItemImageId { get; set; }
|
|
public int ImageIdx { get; set; }
|
|
public String ImageLink { get; set; }
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Article data.
|
|
private class ArticleListData : AjaxResponse_Data.ItemData_Base
|
|
{
|
|
public String OrganisationId { get; set; }
|
|
public String OrganisationName { get; set; }
|
|
public String Headline { get; set; }
|
|
public String Summary { get; set; }
|
|
public class FeedbackData
|
|
{
|
|
public String Summary { get; set; }
|
|
public long? ViewCount { get; set; } = null;
|
|
public long? FeedbackCount { get; set; } = null;
|
|
public String FeedbackAverage { get; set; }
|
|
}
|
|
public FeedbackData Feedback { get; set; }
|
|
public String RegionCaptionPrimary { get; set; }
|
|
public String SportCaptionPrimary { get; set; }
|
|
public String PublishedOnFmt { get; set; }
|
|
}
|
|
private class ArticleItemData : ArticleListData
|
|
{
|
|
public String RegionIdPrimary { get; set; }
|
|
public String SportIdPrimary { get; set; }
|
|
public bool? Published { get; set; } = null;
|
|
public bool? PublishedInFuture { get; set; } = null;
|
|
public String PublishedOn_YYYYMMDD { get; set; }
|
|
public String PublishedOn_Calendar { get; set; }
|
|
public ArticleContentData Content { get; } = new ArticleContentData();
|
|
}
|
|
private void PopulateArticleResponseListData(Model_Article model, SaxArticle article, SaxOrganisation organisation, SaxActivityFeedback activityFeedback, ArticleListData data)
|
|
{
|
|
data.Id = model.EncryptId(article.Id);
|
|
data.OrganisationId = SysId.ToString(article.OrganisationId);
|
|
data.Headline = article.Headline;
|
|
data.Summary = article.Summary;
|
|
PopulateFeedbackResponseListData(activityFeedback, data);
|
|
data.RegionCaptionPrimary = model.GetRegionCaption(article.RegionIdPrimary);
|
|
data.SportCaptionPrimary = model.GetSportCaption(article.SportIdPrimary);
|
|
data.CreatedOnFmt = model.FormatDateForDisplay(article.CreateStamp.On);
|
|
data.PublishedOnFmt = model.FormatDateForDisplay(article.PublishedAt);
|
|
data.StatusCode = article.StatusCode;
|
|
data.StatusDesc = article.StatusDesc;
|
|
}
|
|
private void PopulateArticleResponseItemData(Model_Article model, SaxArticle article, SaxOrganisation organisation, SaxActivityFeedback activityFeedback, ArticleItemData data)
|
|
{
|
|
PopulateArticleResponseListData(model, article, organisation, activityFeedback, data as ArticleListData);
|
|
data.RegionIdPrimary = model.GetEncryptedRegionId(article.RegionIdPrimary);
|
|
data.SportIdPrimary = model.GetEncryptedSportId(article.SportIdPrimary);
|
|
data.Published = DateUtil.IsValid(article.PublishedAt) ? true : (bool?)null;
|
|
data.PublishedInFuture = (data.Published == true && article.PublishedAt != null && ((DateTime)article.PublishedAt).CompareTo(DateTime.Today) >= 0) ? true : (bool?)null;
|
|
data.PublishedOn_YYYYMMDD = DateUtil.Format(article.PublishedAt, "yyyyMMdd");
|
|
data.PublishedOn_Calendar = model.FormatDateForForm(article.PublishedAt);
|
|
|
|
// Article content.
|
|
SaxArticle.ContentData articleContent = SaxArticle.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 = GetItemImageLink(contentSegment.Image.ItemImageId);
|
|
}
|
|
data.Content.Segments.Add(segmentData);
|
|
}
|
|
}
|
|
// Feedback.
|
|
private void PopulateFeedbackResponseData(SaxActivityFeedback activityFeedback, ArticleItemData data)
|
|
{
|
|
PopulateFeedbackResponseListData(activityFeedback, data);
|
|
}
|
|
private void PopulateFeedbackResponseListData(SaxActivityFeedback activityFeedback, ArticleListData data)
|
|
{
|
|
if (activityFeedback == null) return;
|
|
PopulateFeedbackResponseListData(activityFeedback.ViewCount, activityFeedback.FeedbackCount, activityFeedback.FeedbackTotal, activityFeedback.FeedbackAverage, data);
|
|
}
|
|
private void PopulateFeedbackResponseListData(long viewCount, long feedbackCount, long feedbackTotal, decimal feedbackAverage, ArticleListData data)
|
|
{
|
|
if (viewCount <= 0) return;
|
|
data.Feedback = new ArticleListData.FeedbackData();
|
|
data.Feedback.Summary = feedbackCount.ToString() + "/" + viewCount.ToString();
|
|
if (feedbackCount > 0)
|
|
data.Feedback.Summary = feedbackAverage.ToString("N1") + " " + data.Feedback.Summary;
|
|
data.Feedback.ViewCount = viewCount;
|
|
data.Feedback.FeedbackCount = feedbackCount;
|
|
data.Feedback.FeedbackAverage = feedbackAverage.ToString("N1");
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Get Article List ajax request.
|
|
//-------------------------------------------------------------------
|
|
private class OrganisationData : AjaxResponse_Data.ItemIdName { }
|
|
private class AjaxResponse_ArticleList : AjaxResponse_Data
|
|
{
|
|
// Construction.
|
|
public AjaxResponse_ArticleList(SaxModel_Online model) : base(model) { }
|
|
// Article list.
|
|
public List<ArticleListData> Articles { get; } = new List<ArticleListData>();
|
|
/// <summary>Organisations that the user has rights to author articles for.</summary>
|
|
public List<OrganisationData> Organisations { get; set; }
|
|
public ItemIdName DefaultSport { get; set; }
|
|
public ItemIdName DefaultRegion { get; set; }
|
|
public List<String> ImageContentTypes { get; set; }
|
|
}
|
|
//-------------------------------------------------------------------
|
|
[HttpPost]
|
|
[Route("Article/Ajax/GetList")]
|
|
[Route("Article/Ajax/GetArticles")]
|
|
public IActionResult Ajax_GetArticleList(Model_Article model)
|
|
{
|
|
String fn = GetType().Name + ".Ajax_GetArticleList";
|
|
String messageTitle = Localiser["article.caption.articles"];
|
|
AjaxResponse_ArticleList responseData = new AjaxResponse_ArticleList(model);
|
|
|
|
// Validate session and referrer.
|
|
if (!ValidReferer) return Forbid();
|
|
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
|
SaxSessionData_Online sessionData = SessionData_Online;
|
|
SysId userAccountId = SessionUserAccountId;
|
|
RefreshModel(model);
|
|
|
|
// Record disposable items as they are created so all can be disposed when needed.
|
|
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
|
|
{
|
|
// User account.
|
|
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userAccountId);
|
|
disposableItems.Add(userAccount);
|
|
|
|
// Organisations that the user has rights to author articles for.
|
|
foreach (SaxAccountMgr.OrganisationIdName articleOrganisation in AccountMgr.GetOrganisationsForAnyUserRole(userAccountId, new[] { Sax.OrganisationRole.Admin, Sax.OrganisationRole.ArticleAdmin }))
|
|
{
|
|
if (responseData.Organisations == null) responseData.Organisations = new List<OrganisationData>();
|
|
OrganisationData organisationData = new OrganisationData()
|
|
{
|
|
Id = model.EncryptId(articleOrganisation.Id),
|
|
Name = articleOrganisation.Name
|
|
};
|
|
responseData.Organisations.Add(organisationData);
|
|
}
|
|
|
|
// User must either be a journalist or allowed to author organisation articles.
|
|
if (!UserIsJournalist && responseData.Organisations == null) return GetAjaxResult_GoHome(responseData);
|
|
if (UserIsJournalist && responseData.Organisations != null)
|
|
{
|
|
OrganisationData organisationData = new OrganisationData()
|
|
{
|
|
Id = "",
|
|
Name = "No Organisation - Journalist Articles"
|
|
};
|
|
responseData.Organisations.Insert(0, organisationData);
|
|
}
|
|
|
|
// Filter organisation.
|
|
SysId organisationIdFilter = model.ExtractSysId(model.Filter.OrganisationId);
|
|
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationIdFilter);
|
|
disposableItems.Add(organisation);
|
|
|
|
// Default Sport - defined by organisation if selected.
|
|
SysId defaultSportId = organisation?.SportIdPrimary ?? null;
|
|
if (SysId.IsValid(defaultSportId))
|
|
{
|
|
responseData.DefaultSport = new AjaxResponse_Data.ItemIdName()
|
|
{
|
|
Id = model.GetEncryptedSportId(defaultSportId),
|
|
Name = model.GetSportCaption(defaultSportId)
|
|
};
|
|
}
|
|
|
|
// Default region - defined by organisation (if selected) otherwise user themselves.
|
|
SysId defaultRegionId = organisation?.RegionIdPrimary ?? userAccount?.RegionIdCountry ?? null;
|
|
if (SysId.IsValid(defaultRegionId))
|
|
{
|
|
responseData.DefaultRegion = new AjaxResponse_Data.ItemIdName()
|
|
{
|
|
Id = model.GetEncryptedRegionId(defaultRegionId),
|
|
Name = model.GetRegionCaption(defaultRegionId)
|
|
};
|
|
}
|
|
|
|
// Article image content types.
|
|
responseData.ImageContentTypes = SaxItemImage.GetValidContentTypes(Sax.DataItemType.Article);
|
|
|
|
// Item filter.
|
|
SaxArticle.Params prmArticle = new SaxArticle.Params();
|
|
prmArticle.UserAccountIdsAuthorIncluded.Add(sessionData.User.UserAccountId);
|
|
if (SysId.IsValid(organisationIdFilter))
|
|
prmArticle.OrganisationIdsIncluded.Add(organisationIdFilter);
|
|
else
|
|
prmArticle.ExcludeOrganisationArticles = true;
|
|
prmArticle.ArticleFilter = model.Filter.Article;
|
|
prmArticle.StatusCat = SaxArticle.StatusCat.NotDeleted;
|
|
model.Paging.ItemTotal = (ulong)ActivityMgr.GetArticleCount(prmArticle);
|
|
model.PopulatePagingTo(prmArticle.ResultsPaging);
|
|
|
|
// Item columns.
|
|
ColumnDfn colId = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Id);
|
|
ColumnDfn colOrganisationId = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.OrganisationId);
|
|
ColumnDfn colOrganisationName = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Organisation_Name);
|
|
ColumnDfn colHeadline = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Headline);
|
|
ColumnDfn colSummary = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Summary);
|
|
ColumnDfn colRegionIdPrimary = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.RegionIdPrimary);
|
|
ColumnDfn colSportIdPrimary = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.SportIdPrimary);
|
|
ColumnDfn colPublishedOn = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.PublishedAt_Date);
|
|
ColumnDfn colStatus = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Status);
|
|
ColumnDfn colCreatedOn = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.CreateOn_Date);
|
|
// Feedback.
|
|
ColumnDfn colFeedback_ViewCount = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Feedback_ViewCount);
|
|
ColumnDfn colFeedback_FeedbackCount = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Feedback_FeedbackCount);
|
|
ColumnDfn colFeedback_FeedbackTotal = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Feedback_FeedbackTotal);
|
|
ColumnDfn colFeedback_FeedbackAverage = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Feedback_FeedbackAverage);
|
|
List<ColumnDfn> columns = new List<ColumnDfn>()
|
|
{
|
|
colId, colOrganisationId, colOrganisationName, colHeadline, colSummary,
|
|
colRegionIdPrimary, colSportIdPrimary, colPublishedOn,
|
|
colStatus, colCreatedOn,
|
|
colFeedback_ViewCount, colFeedback_FeedbackCount, colFeedback_FeedbackTotal, colFeedback_FeedbackAverage
|
|
};
|
|
|
|
// Sort columns.
|
|
colCreatedOn.SortAscending = false;
|
|
prmArticle.SortColumns.Add(colCreatedOn);
|
|
|
|
// Query items.
|
|
using (DbResultSet resultSet = ActivityMgr.ExecuteArticleQuery(columns, prmArticle))
|
|
{
|
|
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
|
{
|
|
NamedValue.Map values = resultSet.GetRecordValues();
|
|
ArticleListData articleData = new ArticleListData()
|
|
{
|
|
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
|
|
OrganisationId = model.EncryptId(new SysId(values.GetLong(colOrganisationId.DataName))),
|
|
OrganisationName = values.GetString(colOrganisationName.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 = model.FormatDateForDisplay(values.GetDateTime(colCreatedOn.DataName)),
|
|
PublishedOnFmt = model.FormatDateForDisplay(values.GetDateTime(colPublishedOn.DataName)),
|
|
StatusCode = values.GetString(colStatus.DataName),
|
|
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
|
|
};
|
|
// Feedback.
|
|
long viewCount = values.GetLong(colFeedback_ViewCount.DataName);
|
|
if (viewCount > 0)
|
|
PopulateFeedbackResponseListData(viewCount, values.GetLong(colFeedback_FeedbackCount.DataName), values.GetLong(colFeedback_FeedbackTotal.DataName), values.GetDecimal(colFeedback_FeedbackAverage.DataName), articleData);
|
|
responseData.Articles.Add(articleData);
|
|
}
|
|
}
|
|
if (model.Paging.IsPaged)
|
|
model.Paging.ItemCount = (ushort)responseData.Articles.Count;
|
|
}
|
|
|
|
// Complete response.
|
|
responseData.SetPaging(model);
|
|
responseData.Success = true;
|
|
return Json(responseData);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Get Article ajax request.
|
|
//-------------------------------------------------------------------
|
|
private class AjaxResponse_Article : AjaxResponse_Data
|
|
{
|
|
// Construction.
|
|
public AjaxResponse_Article(SaxModel_Online model) : base(model) { }
|
|
// Article details.
|
|
public ArticleItemData Article { get; } = new ArticleItemData();
|
|
}
|
|
//-------------------------------------------------------------------
|
|
[HttpPost]
|
|
[Route("Article/Ajax/Get")]
|
|
[Route("Article/Ajax/GetItem")]
|
|
[Route("Article/Ajax/GetArticle")]
|
|
public IActionResult Ajax_GetArticle(Model_Article model)
|
|
{
|
|
String fn = GetType().Name + ".Ajax_GetArticle";
|
|
String messageTitle = Localiser["article.caption.article"];
|
|
AjaxResponse_Article responseData = new AjaxResponse_Article(model);
|
|
|
|
// Validate session and referrer.
|
|
if (!ValidReferer) return Forbid();
|
|
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
|
SysId userAccountId = SessionUserAccountId;
|
|
RefreshModel(model);
|
|
|
|
// Record disposable items as they are created so all can be disposed when needed.
|
|
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
|
|
{
|
|
|
|
// Identify item.
|
|
SysId articleId = model.ExtractSysId(model.ArticleId);
|
|
if (!SysId.IsValid(articleId))
|
|
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
|
|
|
|
// Retrieve item.
|
|
SaxArticle article = ActivityMgr.GetArticleById(articleId);
|
|
disposableItems.Add(article);
|
|
if (article == null)
|
|
{
|
|
SaLogger.Warn(fn + " Failed to retrieve Article for Id: '" + model.ArticleId + "'");
|
|
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
|
}
|
|
if (!article.UserAccountIdAuthor.Equals(userAccountId))
|
|
{
|
|
SaLogger.Warn(fn + " Inconsistent Article Author. Expected: " + userAccountId + ". Found: " + article.UserAccountIdAuthor);
|
|
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
|
}
|
|
|
|
// Organisation.
|
|
SaxOrganisation organisation = AccountMgr.GetOrganisationById(article.OrganisationId);
|
|
disposableItems.Add(organisation);
|
|
|
|
// Delete upload images.
|
|
SaxItemImage.Params prmItemImage = new SaxItemImage.Params();
|
|
prmItemImage.ItemIdsIncluded.Add(new ItemId((short)Sax.DataItemType.Article, articleId));
|
|
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Article_Upload);
|
|
prmItemImage.StatusCat = SaxItemImage.StatusCat.NotDeleted;
|
|
foreach (SaxItemImage itemImage in ItemDataMgr.GetItemImageList(prmItemImage))
|
|
{
|
|
disposableItems.Add(itemImage);
|
|
itemImage.SessionUid = 0;
|
|
itemImage.ImageUid = 0;
|
|
itemImage.Status = SaStatus.Deleted;
|
|
ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo);
|
|
}
|
|
|
|
// Article Feedback.
|
|
SaxActivityFeedback activityFeedback = ActivityMgr.GetActivityFeedbackForItem(article.GetItemId());
|
|
disposableItems.Add(activityFeedback);
|
|
|
|
// Construct response.
|
|
PopulateArticleResponseItemData(model, article, organisation, activityFeedback, responseData.Article);
|
|
}
|
|
responseData.Success = true;
|
|
return Json(responseData);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Article - Upload Image.
|
|
//-------------------------------------------------------------------
|
|
private class AjaxResponse_UploadArticleImage : AjaxResponse_Data
|
|
{
|
|
// Construction.
|
|
public AjaxResponse_UploadArticleImage(SaxModel_Online model) : base(model) { }
|
|
// Article segment details.
|
|
public ArticleContentSegmentData Segment { get; set; }
|
|
public String UploadMsg { get; set; }
|
|
}
|
|
// Ajax request.
|
|
[HttpPost]
|
|
[Route("Article/Ajax/UploadImage")]
|
|
public IActionResult Ajax_UploadArticleImage(Model_Article model)
|
|
{
|
|
String fn = GetType().Name + ".Ajax_UploadArticleImage";
|
|
AjaxResponse_UploadArticleImage responseData = new AjaxResponse_UploadArticleImage(model);
|
|
String messageTitle = Localiser["article.caption.article"];
|
|
|
|
// Validate session and referrer.
|
|
if (!ValidReferer) return Forbid();
|
|
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
|
SaxSessionData_Online sessionData = SessionData_Online;
|
|
RefreshModel(model);
|
|
|
|
// Record disposable items as they are created so all can be disposed when needed.
|
|
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
|
|
{
|
|
|
|
// 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"]));
|
|
|
|
// Validate mime type.
|
|
if (!SaxItemImage.GetValidMimeTypes(Sax.DataItemType.Article).Contains(imageConfig.MimeType))
|
|
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.invalid.mime"]));
|
|
|
|
// Validate content type.
|
|
if (StringUtil.IsEmpty(file.ContentType) || !SaxItemImage.GetValidContentTypes(Sax.DataItemType.Article).ContainsCI(file.ContentType))
|
|
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.invalid.content.type"]));
|
|
|
|
// Resize the image if required.
|
|
if (!ImageUtils.ConstrainImage(imageConfig, Sax.Config.Articles.Images.WidthMax, Sax.Config.Articles.Images.HeightMax))
|
|
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.resize.failed"]));
|
|
|
|
// 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.ExtractSysId(model.ArticleId);
|
|
ItemId articleItemId = new ItemId((short)Sax.DataItemType.Article, 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.Article_Upload);
|
|
prmItemImage.StatusCat = SaxItemImage.StatusCat.DeletedOnly;
|
|
prmItemImage.ResultsPaging.PageLength = 1;
|
|
prmItemImage.ResultsPaging.PageNo = 1;
|
|
List<SaxItemImage> itemImages = ItemDataMgr.GetItemImageList(prmItemImage);
|
|
SaxItemImage itemImage = !itemImages.IsEmpty() ? itemImages[0] : null;
|
|
if (itemImage == null)
|
|
itemImage = ItemDataMgr.CreateItemImage(imageItemId, Sax.ImageType.Article_Upload);
|
|
disposableItems.Add(itemImage);
|
|
itemImage.ImageType = Sax.ImageType.Article_Upload;
|
|
itemImage.Filename = file.FileName;
|
|
itemImage.Mime = SacsContentTypeHelper.GetMimeTypeFromContentType(file.ContentType).ToString().ToLower();
|
|
itemImage.ContentType = file.ContentType;
|
|
itemImage.ImageData = imageConfig.ImageData;
|
|
itemImage.Width = imageConfig.Width;
|
|
itemImage.Height = imageConfig.Height;
|
|
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 = GetItemImageLink(itemImage.Id);
|
|
responseData.Segment.Image.ImageIdx = itemImage.ImageUid;
|
|
}
|
|
|
|
// Complete response.
|
|
//responseData.SetNotification(messageTitle, "Image uploaded.");
|
|
responseData.Success = true;
|
|
return Json(responseData);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Store Article ajax request.
|
|
//-------------------------------------------------------------------
|
|
[HttpPost]
|
|
[Route("Article/Ajax/Store")]
|
|
[Route("Article/Ajax/StoreItem")]
|
|
[Route("Article/Ajax/StoreArticle")]
|
|
public IActionResult Ajax_StoreArticle(Model_Article model)
|
|
{
|
|
String fn = GetType().Name + ".Ajax_StoreArticle";
|
|
String messageTitle = Localiser["article.caption.article"];
|
|
AjaxResponse_Article responseData = new AjaxResponse_Article(model);
|
|
|
|
// Validate session and referrer.
|
|
if (!ValidReferer) return Forbid();
|
|
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
|
SaxSessionData_Online sessionData = SessionData_Online;
|
|
RefreshModel(model);
|
|
|
|
// Record disposable items as they are created so all can be disposed when needed.
|
|
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
|
|
{
|
|
|
|
// Validate item properties.
|
|
model.ValidateArticleData(responseData);
|
|
if (responseData.HasControlErrors())
|
|
return Json(responseData);
|
|
|
|
// Validate User & Organisation.
|
|
SysId userAccountId = SessionUserAccountId;
|
|
SysId organisationId = model.ExtractSysId(model.ArticleOrganisationId);
|
|
if (SysId.IsValid(organisationId))
|
|
{
|
|
// Organisation specified - User must have Article Admin role.
|
|
if (!AccountMgr.UserHasAnyOrganisationRole(userAccountId, organisationId, new[] { Sax.OrganisationRole.Admin, Sax.OrganisationRole.ArticleAdmin }))
|
|
{
|
|
SaLogger.Warn(fn + " User does not have Article Admin role for organisation." + GetLogSuffix());
|
|
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Organisation not specified - User must be a Journalist.
|
|
if (!UserIsJournalist)
|
|
{
|
|
SaLogger.Warn(fn + " User is not a Journalist for non-organisation article." + GetLogSuffix());
|
|
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
|
|
}
|
|
}
|
|
|
|
// Validate item Id against 'Exists' flag.
|
|
SysId articleId = model.ExtractSysId(model.ArticleId);
|
|
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.
|
|
SaxArticle article = ActivityMgr.GetArticleById(articleId);
|
|
disposableItems.Add(article);
|
|
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"]));
|
|
}
|
|
if (article != null)
|
|
{
|
|
// Validate existing article.
|
|
if (SysId.ToLong(article.UserAccountIdAuthor) != SysId.ToLong(userAccountId))
|
|
{
|
|
SaLogger.Warn(fn + " Inconsistent Article Author." + GetLogSuffix());
|
|
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
|
|
}
|
|
if (SysId.ToLong(article.OrganisationId) != SysId.ToLong(organisationId))
|
|
{
|
|
SaLogger.Warn(fn + " Inconsistent Article Organisation." + GetLogSuffix());
|
|
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
|
|
}
|
|
}
|
|
|
|
// Organisation.
|
|
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
|
|
disposableItems.Add(organisation);
|
|
|
|
// Create new item if required.
|
|
if (article == null)
|
|
{
|
|
article = ActivityMgr.CreateArticle(sessionData.User.UserAccountId, organisationId);
|
|
disposableItems.Add(article);
|
|
}
|
|
|
|
// Populate item properties.
|
|
if (model.ArticleRegionId != null)
|
|
article.RegionIdPrimary = model.ExtractSysId(model.ArticleRegionId);
|
|
if (model.ArticleSportId != null)
|
|
article.SportIdPrimary = model.ExtractSysId(model.ArticleSportId);
|
|
if (model.ArticleHeadline != null)
|
|
article.Headline = model.ArticleHeadline;
|
|
if (model.ArticleSummary != null)
|
|
article.Summary = model.ArticleSummary;
|
|
if (!model.ArticlePublished.ToBool())
|
|
{
|
|
if (model.ArticlePublish)
|
|
article.PublishedAt = model.GetArticlePublishOn();
|
|
else
|
|
article.PublishedAt = null;
|
|
}
|
|
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.Article_Upload);
|
|
prmItemImage.StatusCat = SaxItemImage.StatusCat.NotDeleted;
|
|
List<SaxItemImage> itemImages = ItemDataMgr.GetItemImageList(prmItemImage);
|
|
disposableItems.AddRange(itemImages);
|
|
|
|
// If Article doesn't yet exist, create it now so that images can be assigned.
|
|
bool articleCreated = false;
|
|
if (!SysId.IsValid(article.Id))
|
|
{
|
|
if (!ActivityMgr.InsertArticle(article, DbUpdateInfo))
|
|
{
|
|
SaLogger.Warn(fn + " Failed to insert article: " + ActivityMgr.ErrorStateMessage);
|
|
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
|
|
}
|
|
articleCreated = true;
|
|
}
|
|
|
|
// Content.
|
|
if (model.ArticleContentSegmentBodies != null)
|
|
{
|
|
SaxArticle.ContentData articleContent = new SaxArticle.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 = model.ExtractSysId(segmentItemImageId);
|
|
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 = SaxArticle.GetContentData(articleContent);
|
|
}
|
|
|
|
// Assign/Validate thumbnail image.
|
|
// Compile list of images in the article body and make a note of the first image.
|
|
SysId itemIdImage_First = null;
|
|
List<SysId> itemImageIds = new List<SysId>();
|
|
foreach (var contentSegment in SaxArticle.ExtractContent(article.BodyData).Segments)
|
|
{
|
|
var segmentData = new ArticleContentSegmentData();
|
|
if (contentSegment.Image.IsValid())
|
|
{
|
|
if (!SysId.IsValid(itemIdImage_First))
|
|
itemIdImage_First = contentSegment.Image.ItemImageId;
|
|
itemImageIds.AddId(contentSegment.Image.ItemImageId);
|
|
}
|
|
}
|
|
// If the thumbnail isn't found in the body clear it.
|
|
if (!itemImageIds.Contains(article.ItemImageIdThumbnail))
|
|
article.ItemImageIdThumbnail = null;
|
|
// If the thumbnail isn't assigned, assign the first image.
|
|
if (!SysId.IsValid(article.ItemImageIdThumbnail))
|
|
article.ItemImageIdThumbnail = itemIdImage_First;
|
|
|
|
// Update the Article.
|
|
if (!ActivityMgr.UpdateArticle(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"]));
|
|
}
|
|
SaLogger.Info(fn + " Article " + (articleCreated ? "created" : "updated") + ". Id: " + article.Id.ToString() + ". User: " + SessionData.User.EmailAddress, true);
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Article Feedback.
|
|
SaxActivityFeedback activityFeedback = ActivityMgr.GetActivityFeedbackForItem(article.GetItemId());
|
|
disposableItems.Add(activityFeedback);
|
|
|
|
// Construct response.
|
|
PopulateArticleResponseItemData(model, article, organisation, activityFeedback, responseData.Article);
|
|
responseData.Success = true;
|
|
}
|
|
return Json(responseData);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|