diff --git a/src/Modules/Grand.Module.Installer/Extensions/PermissionExtensions.cs b/src/Modules/Grand.Module.Installer/Extensions/PermissionExtensions.cs index 729f88c5b..0638e318d 100644 --- a/src/Modules/Grand.Module.Installer/Extensions/PermissionExtensions.cs +++ b/src/Modules/Grand.Module.Installer/Extensions/PermissionExtensions.cs @@ -212,7 +212,8 @@ public static IEnumerable DefaultPermissions() StandardPermission.ManageShipments, StandardPermission.ManageMerchandiseReturns, StandardPermission.ManageCheckoutAttribute, - StandardPermission.ManageReports + StandardPermission.ManageReports, + StandardPermission.ManageNews ] }, diff --git a/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/Content.TabNewsSettings.cshtml b/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/Content.TabNewsSettings.cshtml index 580e0d861..1defad5c8 100644 --- a/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/Content.TabNewsSettings.cshtml +++ b/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/Content.TabNewsSettings.cshtml @@ -67,6 +67,6 @@ - + \ No newline at end of file diff --git a/src/Web/Grand.Web.Admin/Controllers/NewsController.cs b/src/Web/Grand.Web.Admin/Controllers/NewsController.cs index 4c4e9b8d3..e84b124b7 100644 --- a/src/Web/Grand.Web.Admin/Controllers/NewsController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/NewsController.cs @@ -203,8 +203,7 @@ public IActionResult Comments(string filterByNewsItemId) [HttpPost] public async Task Comments(string filterByNewsItemId, DataSourceRequest command) { - var comments = - await _newsViewModelService.PrepareNewsCommentModel(filterByNewsItemId, command.Page, command.PageSize); + var comments = await _newsViewModelService.PrepareNewsCommentModel(filterByNewsItemId, command.Page, command.PageSize); var gridModel = new DataSourceResult { Data = comments.newsCommentModels.ToList(), diff --git a/src/Web/Grand.Web.AdminShared/Models/News/NewsItemListModel.cs b/src/Web/Grand.Web.AdminShared/Models/News/NewsItemListModel.cs index 14e25b1c6..97f8f8856 100644 --- a/src/Web/Grand.Web.AdminShared/Models/News/NewsItemListModel.cs +++ b/src/Web/Grand.Web.AdminShared/Models/News/NewsItemListModel.cs @@ -9,5 +9,8 @@ public class NewsItemListModel : BaseModel [GrandResourceDisplayName("Admin.Content.News.NewsItems.List.SearchStore")] public string SearchStoreId { get; set; } + [GrandResourceDisplayName("Admin.Content.News.NewsItems.List.SearchTitle")] + public string SearchNewsTitle { get; set; } + public IList AvailableStores { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Web/Grand.Web.AdminShared/Services/NewsViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/NewsViewModelService.cs index a58b1d3df..e4c6eede6 100644 --- a/src/Web/Grand.Web.AdminShared/Services/NewsViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/NewsViewModelService.cs @@ -49,7 +49,7 @@ public NewsViewModelService(INewsService newsService, public virtual async Task<(IEnumerable newsItemModels, int totalCount)> PrepareNewsItemModel( NewsItemListModel model, int pageIndex, int pageSize) { - var news = await _newsService.GetAllNews(model.SearchStoreId, pageIndex - 1, pageSize, true, true); + var news = await _newsService.GetAllNews(model.SearchStoreId, pageIndex - 1, pageSize, true, true, model.SearchNewsTitle); return (news.Select(x => { var m = x.ToModel(_dateTimeService); diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/News/Create.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Create.cshtml new file mode 100644 index 000000000..53e393735 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Create.cshtml @@ -0,0 +1,38 @@ +@model NewsItemModel +@{ + //page title + ViewBag.Title = Loc["Admin.Content.News.NewsItems.AddNew"]; + Layout = Constants.LayoutStore; +} +
+ +
+
+
+
+
+ + @Loc["Admin.Content.News.NewsItems.AddNew"] + + @Html.ActionLink(Loc["Admin.Content.News.NewsItems.BackToList"], "List") + +
+
+
+ + + +
+
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/News/Edit.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Edit.cshtml new file mode 100644 index 000000000..2e2ea0f40 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Edit.cshtml @@ -0,0 +1,46 @@ +@model NewsItemModel +@{ + //page title + ViewBag.Title = Loc["Admin.Content.News.NewsItems.EditNewsItemDetails"]; + Layout = Constants.LayoutStore; +} +
+ +
+
+
+
+
+ + @Loc["Admin.Content.News.NewsItems.EditNewsItemDetails"] - @Model.Title + + @Html.ActionLink(Loc["Admin.Content.News.NewsItems.BackToList"], "List") + +
+
+
+ + + + + @Loc["Admin.Common.Delete"] + + +
+
+
+
+ +
+
+
+
+
+ \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/News/List.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/News/List.cshtml new file mode 100644 index 000000000..c2eb9c2c0 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/News/List.cshtml @@ -0,0 +1,131 @@ +@model NewsItemListModel +@inject AdminAreaSettings adminAreaSettings +@{ + //page title + ViewBag.Title = Loc["Admin.Content.News.NewsItems"]; + Layout = Constants.LayoutStore; +} + +
+
+
+
+
+ + @Loc["Admin.Content.News.NewsItems"] +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.TabComments.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.TabComments.cshtml new file mode 100644 index 000000000..266dc45ae --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.TabComments.cshtml @@ -0,0 +1,96 @@ +@model NewsItemModel +@inject AdminAreaSettings adminAreaSettings + +@if (!string.IsNullOrEmpty(Model.Id)) +{ +
+ +
+
+
+ +
+ +} +else +{ +
+ @Loc["Admin.Content.News.NewsItems.SaveBeforeEdit"] +
+} \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.TabInfo.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.TabInfo.cshtml new file mode 100644 index 000000000..c0a489daa --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.TabInfo.cshtml @@ -0,0 +1,101 @@ +@using Microsoft.AspNetCore.Mvc.Razor +@model NewsItemModel + +@{ + Func + template = @
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+ +
; +} + +
+ +
+ +
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.TabSeo.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.TabSeo.cshtml new file mode 100644 index 000000000..2f504cbfe --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.TabSeo.cshtml @@ -0,0 +1,75 @@ +@using Microsoft.AspNetCore.Mvc.Razor +@model NewsItemModel + + +@{ + Func + template = @
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+ +
; +} + +
+ +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+ \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.cshtml new file mode 100644 index 000000000..9e3c8074e --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/News/Partials/CreateOrUpdate.cshtml @@ -0,0 +1,30 @@ +@model NewsItemModel + +
+ + + + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ +
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml index baff7dc33..d9c332025 100644 --- a/src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml @@ -29,6 +29,7 @@ @using Grand.Web.AdminShared.Models.Common @using Grand.Web.AdminShared.Models.Customers @using Grand.Web.AdminShared.Models.Cms +@using Grand.Web.AdminShared.Models.News @inject LocService Loc @inject IEnumTranslationService EnumTranslationService \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Controllers/NewsController.cs b/src/Web/Grand.Web.Store/Controllers/NewsController.cs new file mode 100644 index 000000000..4f16e8a2c --- /dev/null +++ b/src/Web/Grand.Web.Store/Controllers/NewsController.cs @@ -0,0 +1,239 @@ +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Cms; +using Grand.Business.Core.Interfaces.Common.Configuration; +using Grand.Business.Core.Interfaces.Common.Directory; +using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Domain.News; +using Grand.Domain.Permissions; +using Grand.Infrastructure; +using Grand.Web.AdminShared.Extensions; +using Grand.Web.AdminShared.Extensions.Mapping; +using Grand.Web.AdminShared.Interfaces; +using Grand.Web.AdminShared.Models.News; +using Grand.Web.Common.DataSource; +using Grand.Web.Common.Filters; +using Grand.Web.Common.Security.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Grand.Web.Store.Controllers; + +[PermissionAuthorize(PermissionSystemName.News)] +public class NewsController : BaseStoreController +{ + #region Constructors + + public NewsController( + INewsViewModelService newsViewModelService, + INewsService newsService, + ILanguageService languageService, + ITranslationService translationService, + ISettingService settingService, + IDateTimeService dateTimeService, + IContextAccessor contextAccessor) + { + _newsViewModelService = newsViewModelService; + _newsService = newsService; + _languageService = languageService; + _translationService = translationService; + _settingService = settingService; + _dateTimeService = dateTimeService; + _contextAccessor = contextAccessor; + } + + #endregion + + #region Fields + + private readonly INewsViewModelService _newsViewModelService; + private readonly INewsService _newsService; + private readonly ILanguageService _languageService; + private readonly ITranslationService _translationService; + private readonly ISettingService _settingService; + private readonly IDateTimeService _dateTimeService; + private readonly IContextAccessor _contextAccessor; + + #endregion + + #region News items + + public IActionResult Index() + { + return RedirectToAction("List"); + } + + public IActionResult List() + { + var model = new NewsItemListModel(); + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.List)] + [HttpPost] + public async Task List(DataSourceRequest command, NewsItemListModel model) + { + var storeId = _contextAccessor.StoreContext.CurrentStore.Id; + var newsSettings = await _settingService.LoadSetting(storeId); + + var news = await _newsService.GetAllNews(storeId, command.Page - 1, command.PageSize, newsTitle: model.SearchNewsTitle); + + var gridModel = new DataSourceResult { + Data = news.Select(x => + { + var m = x.ToModel(_dateTimeService); + m.Full = ""; + m.CreatedOn = _dateTimeService.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + m.Comments = x.CommentCount; + return m; + }).ToList(), + Total = news.TotalCount + }; + return Json(gridModel); + } + + [PermissionAuthorizeAction(PermissionActionName.Create)] + public async Task Create() + { + ViewBag.AllLanguages = _languageService.GetAllLanguages(true); + var model = new NewsItemModel { + //default values + Published = true, + AllowComments = true + }; + + //locales + await AddLocales(_languageService, model.Locales); + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.Edit)] + [HttpPost] + [ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + public async Task Create(NewsItemModel model, bool continueEditing) + { + if (ModelState.IsValid) + { + model.Stores = [_contextAccessor.WorkContext.CurrentCustomer.StaffStoreId]; + var newsItem = await _newsViewModelService.InsertNewsItemModel(model); + await _newsService.UpdateNews(newsItem); + + Success(_translationService.GetResource("Admin.Content.News.NewsItems.Added")); + return continueEditing ? RedirectToAction("Edit", new { id = newsItem.Id }) : RedirectToAction("List"); + } + + //If we got this far, something failed, redisplay form + ViewBag.AllLanguages = _languageService.GetAllLanguages(true); + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.Preview)] + public async Task Edit(string id) + { + var newsItem = await _newsService.GetNewsById(id); + if (newsItem == null) + //No news item found with the specified id + return RedirectToAction("List"); + + if (!newsItem.LimitedToStores || (newsItem.LimitedToStores && + newsItem.Stores.Contains(_contextAccessor.WorkContext.CurrentCustomer.StaffStoreId) && + newsItem.Stores.Count > 1)) + { + Warning(_translationService.GetResource("Admin.Content.News.Permissions")); + } + else + { + if (!newsItem.AccessToEntityByStore(_contextAccessor.WorkContext.CurrentCustomer.StaffStoreId)) + return RedirectToAction("List"); + } + + ViewBag.AllLanguages = await _languageService.GetAllLanguages(true); + var model = newsItem.ToModel(_dateTimeService); + //locales + await AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.Title = newsItem.GetTranslation(x => x.Title, languageId, false); + locale.Short = newsItem.GetTranslation(x => x.Short, languageId, false); + locale.Full = newsItem.GetTranslation(x => x.Full, languageId, false); + locale.MetaKeywords = newsItem.GetTranslation(x => x.MetaKeywords, languageId, false); + locale.MetaDescription = newsItem.GetTranslation(x => x.MetaDescription, languageId, false); + locale.MetaTitle = newsItem.GetTranslation(x => x.MetaTitle, languageId, false); + locale.SeName = newsItem.GetSeName(languageId, false); + }); + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.Edit)] + [HttpPost] + [ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + public async Task Edit(NewsItemModel model, bool continueEditing) + { + var newsItem = await _newsService.GetNewsById(model.Id); + if (newsItem == null) + //No news item found with the specified id + return RedirectToAction("List"); + + if (!newsItem.AccessToEntityByStore(_contextAccessor.WorkContext.CurrentCustomer.StaffStoreId)) + return RedirectToAction("Edit", new { id = newsItem.Id }); + + if (ModelState.IsValid) + { + model.Stores = [_contextAccessor.WorkContext.CurrentCustomer.StaffStoreId]; + newsItem = await _newsViewModelService.UpdateNewsItemModel(newsItem, model); + Success(_translationService.GetResource("Admin.Content.News.NewsItems.Updated")); + + if (continueEditing) + { + //selected tab + await SaveSelectedTabIndex(); + + return RedirectToAction("Edit", new { id = newsItem.Id }); + } + + return RedirectToAction("List"); + } + + //If we got this far, something failed, redisplay form + ViewBag.AllLanguages = await _languageService.GetAllLanguages(true); + + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.Delete)] + [HttpPost] + public async Task Delete(string id) + { + var newsItem = await _newsService.GetNewsById(id); + if (newsItem == null) + //No news item found with the specified id + return RedirectToAction("List"); + + if (!newsItem.AccessToEntityByStore(_contextAccessor.WorkContext.CurrentCustomer.StaffStoreId)) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + await _newsService.DeleteNews(newsItem); + + Success(_translationService.GetResource("Admin.Content.News.NewsItems.Deleted")); + return RedirectToAction("List"); + } + + Error(ModelState); + return RedirectToAction("Edit", new { id = newsItem.Id }); + } + + [PermissionAuthorizeAction(PermissionActionName.Preview)] + public async Task Preview(string id) + { + var newsItem = await _newsService.GetNewsById(id); + if (newsItem == null) + return RedirectToAction("List"); + + if (!newsItem.AccessToEntityByStore(_contextAccessor.WorkContext.CurrentCustomer.StaffStoreId)) + return RedirectToAction("List"); + + var model = newsItem.ToModel(_dateTimeService); + return View(model); + } + + #endregion +} \ No newline at end of file diff --git a/src/Web/Grand.Web/App_Data/Resources/DefaultLanguage.xml b/src/Web/Grand.Web/App_Data/Resources/DefaultLanguage.xml index 8b59be237..3b37fcbdf 100644 Binary files a/src/Web/Grand.Web/App_Data/Resources/DefaultLanguage.xml and b/src/Web/Grand.Web/App_Data/Resources/DefaultLanguage.xml differ