diff --git a/src/Modules/Grand.Module.Installer/Extensions/PermissionExtensions.cs b/src/Modules/Grand.Module.Installer/Extensions/PermissionExtensions.cs index e4a6d1e4f..f998b7cb9 100644 --- a/src/Modules/Grand.Module.Installer/Extensions/PermissionExtensions.cs +++ b/src/Modules/Grand.Module.Installer/Extensions/PermissionExtensions.cs @@ -201,6 +201,7 @@ public static IEnumerable DefaultPermissions() StandardPermission.ManageProducts, StandardPermission.ManageProductAttributes, StandardPermission.ManageSpecificationAttributes, + StandardPermission.ManageProductReviews, StandardPermission.ManageFiles, StandardPermission.ManagePictures, StandardPermission.ManageCategories, diff --git a/src/Web/Grand.Web.Admin/Controllers/ProductReviewController.cs b/src/Web/Grand.Web.Admin/Controllers/ProductReviewController.cs index c3d140449..ff6657f4f 100644 --- a/src/Web/Grand.Web.Admin/Controllers/ProductReviewController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/ProductReviewController.cs @@ -1,8 +1,6 @@ using Grand.Business.Core.Interfaces.Catalog.Products; -using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Permissions; -using Grand.Infrastructure; using Grand.Web.AdminShared.Interfaces; using Grand.Web.AdminShared.Models.Catalog; using Grand.Web.Common.DataSource; @@ -20,15 +18,11 @@ public class ProductReviewController : BaseAdminController public ProductReviewController( IProductReviewViewModelService productReviewViewModelService, IProductReviewService productReviewService, - ITranslationService translationService, - IContextAccessor contextAccessor, - IGroupService groupService) + ITranslationService translationService) { _productReviewViewModelService = productReviewViewModelService; _productReviewService = productReviewService; _translationService = translationService; - _contextAccessor = contextAccessor; - _groupService = groupService; } #endregion @@ -38,8 +32,6 @@ public ProductReviewController( private readonly IProductReviewViewModelService _productReviewViewModelService; private readonly IProductReviewService _productReviewService; private readonly ITranslationService _translationService; - private readonly IContextAccessor _contextAccessor; - private readonly IGroupService _groupService; #endregion Fields @@ -53,8 +45,7 @@ public IActionResult Index() public async Task List() { - var model = await _productReviewViewModelService.PrepareProductReviewListModel(_contextAccessor.WorkContext.CurrentCustomer - .StaffStoreId); + var model = await _productReviewViewModelService.PrepareProductReviewListModel(); return View(model); } @@ -62,10 +53,6 @@ public async Task List() [HttpPost] public async Task List(DataSourceRequest command, ProductReviewListModel model) { - //limit for store manager - if (await _groupService.IsStoreManager(_contextAccessor.WorkContext.CurrentCustomer)) - model.SearchStoreId = _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId; - var (productReviewModels, totalCount) = await _productReviewViewModelService.PrepareProductReviewsModel(model, command.Page, command.PageSize); var gridModel = new DataSourceResult { @@ -86,9 +73,6 @@ public async Task Edit(string id) //No product review found with the specified id return RedirectToAction("List"); - if (await _groupService.IsStoreManager(_contextAccessor.WorkContext.CurrentCustomer) && - productReview.StoreId != _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId) return RedirectToAction("List"); - var model = new ProductReviewModel(); await _productReviewViewModelService.PrepareProductReviewModel(model, productReview, false, false); return View(model); @@ -104,15 +88,12 @@ public async Task Edit(ProductReviewModel model, bool continueEdi //No product review found with the specified id return RedirectToAction("List"); - if (await _groupService.IsStoreManager(_contextAccessor.WorkContext.CurrentCustomer) && - productReview.StoreId != _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId) return RedirectToAction("List"); - if (ModelState.IsValid) { productReview = await _productReviewViewModelService.UpdateProductReview(productReview, model); Success(_translationService.GetResource("Admin.Catalog.ProductReviews.Updated")); return continueEditing - ? RedirectToAction("Edit", new { id = productReview.Id, productReview.ProductId }) + ? RedirectToAction("Edit", new { productReview.Id, productReview.ProductId }) : RedirectToAction("List"); } @@ -131,9 +112,6 @@ public async Task Delete(string id) //No product review found with the specified id return RedirectToAction("List"); - if (await _groupService.IsStoreManager(_contextAccessor.WorkContext.CurrentCustomer) && - productReview.StoreId != _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId) return RedirectToAction("List"); - if (ModelState.IsValid) { await _productReviewViewModelService.DeleteProductReview(productReview); @@ -150,8 +128,7 @@ public async Task Delete(string id) public async Task ApproveSelected(ICollection selectedIds) { if (selectedIds != null) - await _productReviewViewModelService.ApproveSelected(selectedIds.ToList(), - _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId); + await _productReviewViewModelService.ApproveSelected(selectedIds.ToList()); return Json(new { Result = true }); } @@ -161,8 +138,7 @@ await _productReviewViewModelService.ApproveSelected(selectedIds.ToList(), public async Task DisapproveSelected(ICollection selectedIds) { if (selectedIds != null) - await _productReviewViewModelService.DisapproveSelected(selectedIds.ToList(), - _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId); + await _productReviewViewModelService.DisapproveSelected(selectedIds.ToList()); return Json(new { Result = true }); } @@ -175,23 +151,19 @@ public async Task ProductSearchAutoComplete(string term, if (string.IsNullOrWhiteSpace(term) || term.Length < searchTermMinimumLength) return Content(""); - var storeId = string.Empty; - if (await _groupService.IsStoreManager(_contextAccessor.WorkContext.CurrentCustomer)) - storeId = _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId; - //products const int productNumber = 15; var products = (await productService.SearchProducts( - storeId: storeId, keywords: term, pageSize: productNumber, showHidden: true)).products; var result = (from p in products - select new { - label = p.Name, - productid = p.Id - }) + select new + { + label = p.Name, + productid = p.Id + }) .ToList(); return Json(result); } diff --git a/src/Web/Grand.Web.AdminShared/Interfaces/IProductReviewViewModelService.cs b/src/Web/Grand.Web.AdminShared/Interfaces/IProductReviewViewModelService.cs index cbb6085b0..0a1b0a308 100644 --- a/src/Web/Grand.Web.AdminShared/Interfaces/IProductReviewViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Interfaces/IProductReviewViewModelService.cs @@ -12,8 +12,8 @@ Task PrepareProductReviewModel(ProductReviewModel model, ProductReviewListModel model, int pageIndex, int pageSize); Task UpdateProductReview(ProductReview productReview, ProductReviewModel model); - Task PrepareProductReviewListModel(string storeId); + Task PrepareProductReviewListModel(string storeId = ""); Task DeleteProductReview(ProductReview productReview); - Task ApproveSelected(IEnumerable selectedIds, string storeId); - Task DisapproveSelected(IEnumerable selectedIds, string storeId); + Task ApproveSelected(IEnumerable selectedIds, string storeId = ""); + Task DisapproveSelected(IEnumerable selectedIds, string storeId = ""); } \ No newline at end of file diff --git a/src/Web/Grand.Web.AdminShared/Services/ProductReviewViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/ProductReviewViewModelService.cs index 2bf7ebb80..a94ead0cb 100644 --- a/src/Web/Grand.Web.AdminShared/Services/ProductReviewViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/ProductReviewViewModelService.cs @@ -84,7 +84,7 @@ public virtual async Task PrepareProductReviewModel(ProductReviewModel model, } } - public virtual async Task PrepareProductReviewListModel(string storeId) + public virtual async Task PrepareProductReviewListModel(string storeId = "") { var model = new ProductReviewListModel(); @@ -146,7 +146,7 @@ public virtual async Task DeleteProductReview(ProductReview productReview) await _mediator.Send(new UpdateProductReviewTotalsCommand { Product = product }); } - public virtual async Task ApproveSelected(IEnumerable selectedIds, string storeId) + public virtual async Task ApproveSelected(IEnumerable selectedIds, string storeId = "") { foreach (var id in selectedIds) { @@ -170,7 +170,7 @@ public virtual async Task ApproveSelected(IEnumerable selectedIds, strin } } - public virtual async Task DisapproveSelected(IEnumerable selectedIds, string storeId) + public virtual async Task DisapproveSelected(IEnumerable selectedIds, string storeId = "") { foreach (var id in selectedIds) { diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/ProductReview/Edit.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/ProductReview/Edit.cshtml new file mode 100644 index 000000000..b99753717 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/ProductReview/Edit.cshtml @@ -0,0 +1,41 @@ +@model ProductReviewModel +@{ + //page title + ViewBag.Title = Loc["Admin.Catalog.ProductReviews.EditProductReviewDetails"]; +} +
+ +
+
+
+
+
+ + @Loc["Admin.Catalog.ProductReviews.EditProductReviewDetails"] + + @Html.ActionLink(Loc["Admin.Catalog.ProductReviews.BackToList"], "List") + +
+
+
+ + + + @Loc["Admin.Common.Delete"] + + +
+
+
+
+ +
+
+
+
+
+ \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/ProductReview/List.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/ProductReview/List.cshtml new file mode 100644 index 000000000..cef7a7222 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/ProductReview/List.cshtml @@ -0,0 +1,317 @@ +@model ProductReviewListModel +@inject AdminAreaSettings adminAreaSettings +@{ + //page title + ViewBag.Title = Loc["Admin.Catalog.ProductReviews"]; +} + +
+
+ +
+
+ + + \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/ProductReview/Partials/CreateOrUpdate.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/ProductReview/Partials/CreateOrUpdate.cshtml new file mode 100644 index 000000000..abf7decf4 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/ProductReview/Partials/CreateOrUpdate.cshtml @@ -0,0 +1,78 @@ +@model ProductReviewModel +
+ + +
+ +
+
+ +
+ +
+
+
+ +
+ @Model.StoreName +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Controllers/ProductController.cs b/src/Web/Grand.Web.Store/Controllers/ProductController.cs index 02d532b6a..a0a39345a 100644 --- a/src/Web/Grand.Web.Store/Controllers/ProductController.cs +++ b/src/Web/Grand.Web.Store/Controllers/ProductController.cs @@ -1,5 +1,4 @@ -using Grand.Business.Catalog.Services.Products; -using Grand.Business.Core.Extensions; +using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; @@ -16,7 +15,6 @@ using Grand.Web.AdminShared.Interfaces; using Grand.Web.AdminShared.Models.Catalog; using Grand.Web.AdminShared.Models.Orders; -using Grand.Web.Common; using Grand.Web.Common.DataSource; using Grand.Web.Common.Extensions; using Grand.Web.Common.Filters; @@ -275,7 +273,7 @@ public async Task CopyProduct(ProductModel model, { var originalProduct = await _productService.GetProductById(copyModel.Id, true); - if (!originalProduct.AccessToEntityByStore(_contextAccessor.WorkContext.CurrentCustomer.StaffStoreId)) + if (originalProduct.LimitedToStores && !originalProduct.Stores.Contains(_contextAccessor.WorkContext.CurrentCustomer.StaffStoreId)) return RedirectToAction("List"); originalProduct.LimitedToStores = true; @@ -1452,7 +1450,7 @@ public async Task BulkEditUpdate(IEnumerable BulkEditDelete(IEnumerable products) diff --git a/src/Web/Grand.Web.Store/Controllers/ProductReviewController.cs b/src/Web/Grand.Web.Store/Controllers/ProductReviewController.cs new file mode 100644 index 000000000..087e0de8f --- /dev/null +++ b/src/Web/Grand.Web.Store/Controllers/ProductReviewController.cs @@ -0,0 +1,187 @@ +using Grand.Business.Core.Interfaces.Catalog.Products; +using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Domain.Permissions; +using Grand.Infrastructure; +using Grand.Web.AdminShared.Interfaces; +using Grand.Web.AdminShared.Models.Catalog; +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.ProductReviews)] +public class ProductReviewController : BaseStoreController +{ + #region Constructors + + public ProductReviewController( + IProductReviewViewModelService productReviewViewModelService, + IProductReviewService productReviewService, + ITranslationService translationService, + IContextAccessor contextAccessor) + { + _productReviewViewModelService = productReviewViewModelService; + _productReviewService = productReviewService; + _translationService = translationService; + _contextAccessor = contextAccessor; + } + + #endregion + + #region Fields + + private readonly IProductReviewViewModelService _productReviewViewModelService; + private readonly IProductReviewService _productReviewService; + private readonly ITranslationService _translationService; + private readonly IContextAccessor _contextAccessor; + + #endregion Fields + + #region Methods + + //list + public IActionResult Index() + { + return RedirectToAction("List"); + } + + public IActionResult List() + { + var model = new ProductReviewListModel(); + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.List)] + [HttpPost] + public async Task List(DataSourceRequest command, ProductReviewListModel model) + { + model.SearchStoreId = _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId; + var (productReviewModels, totalCount) = await _productReviewViewModelService.PrepareProductReviewsModel(model, command.Page, command.PageSize); + var gridModel = new DataSourceResult { + Data = productReviewModels.ToList(), + Total = totalCount + }; + + return Json(gridModel); + } + + //edit + [PermissionAuthorizeAction(PermissionActionName.Preview)] + public async Task Edit(string id) + { + var productReview = await _productReviewService.GetProductReviewById(id); + + if (productReview == null) + //No product review found with the specified id + return RedirectToAction("List"); + + if (productReview.StoreId != _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId) return RedirectToAction("List"); + + var model = new ProductReviewModel(); + await _productReviewViewModelService.PrepareProductReviewModel(model, productReview, false, false); + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.Edit)] + [HttpPost] + [ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + public async Task Edit(ProductReviewModel model, bool continueEditing) + { + var productReview = await _productReviewService.GetProductReviewById(model.Id); + if (productReview == null) + //No product review found with the specified id + return RedirectToAction("List"); + + if (productReview.StoreId != _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId) return RedirectToAction("List"); + + if (ModelState.IsValid) + { + productReview = await _productReviewViewModelService.UpdateProductReview(productReview, model); + Success(_translationService.GetResource("Admin.Catalog.ProductReviews.Updated")); + return continueEditing + ? RedirectToAction("Edit", new { productReview.Id, productReview.ProductId }) + : RedirectToAction("List"); + } + + //If we got this far, something failed, redisplay form + await _productReviewViewModelService.PrepareProductReviewModel(model, productReview, true, false); + return View(model); + } + + //delete + [PermissionAuthorizeAction(PermissionActionName.Delete)] + [HttpPost] + public async Task Delete(string id) + { + var productReview = await _productReviewService.GetProductReviewById(id); + if (productReview == null) + //No product review found with the specified id + return RedirectToAction("List"); + + if (productReview.StoreId != _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId) return RedirectToAction("List"); + + if (ModelState.IsValid) + { + await _productReviewViewModelService.DeleteProductReview(productReview); + Success(_translationService.GetResource("Admin.Catalog.ProductReviews.Deleted")); + return RedirectToAction("List"); + } + + Error(ModelState); + return RedirectToAction("Edit", new { id = productReview.Id }); + } + + [PermissionAuthorizeAction(PermissionActionName.Edit)] + [HttpPost] + public async Task ApproveSelected(ICollection selectedIds) + { + if (selectedIds != null) + await _productReviewViewModelService.ApproveSelected(selectedIds.ToList(), + _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId); + + return Json(new { Result = true }); + } + + [PermissionAuthorizeAction(PermissionActionName.Edit)] + [HttpPost] + public async Task DisapproveSelected(ICollection selectedIds) + { + if (selectedIds != null) + await _productReviewViewModelService.DisapproveSelected(selectedIds.ToList(), + _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId); + + return Json(new { Result = true }); + } + + + public async Task ProductSearchAutoComplete(string term, + [FromServices] IProductService productService) + { + const int searchTermMinimumLength = 3; + if (string.IsNullOrWhiteSpace(term) || term.Length < searchTermMinimumLength) + return Content(""); + + var storeId = _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId; + + //products + const int productNumber = 15; + var products = (await productService.SearchProducts( + storeId: storeId, + keywords: term, + pageSize: productNumber, + showHidden: true)).products; + + var result = (from p in products + select new + { + label = p.Name, + productid = p.Id + }) + .ToList(); + return Json(result); + } + + #endregion +} \ No newline at end of file