Try your search with a different keyword or use * as a wildcard.
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Discounts;
using Nop.Services.Catalog;
using Nop.Services.Discounts;
using Nop.Services.ExportImport;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Media;
using Nop.Services.Messages;
using Nop.Services.Security;
using Nop.Services.Seo;
using Nop.Services.Stores;
using Nop.Web.Areas.Admin.Factories;
using Nop.Web.Areas.Admin.Infrastructure.Mapper.Extensions;
using Nop.Web.Areas.Admin.Models.Catalog;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Factories;
using Nop.Web.Framework.Models.Translation;
using Nop.Web.Framework.Mvc;
using Nop.Web.Framework.Mvc.Filters;
namespace Nop.Web.Areas.Admin.Controllers;
public partial class CategoryController : BaseAdminController
{
#region Fields
protected readonly ICategoryModelFactory _categoryModelFactory;
protected readonly ICategoryService _categoryService;
protected readonly ICustomerActivityService _customerActivityService;
protected readonly IDiscountService _discountService;
protected readonly IExportManager _exportManager;
protected readonly IImportManager _importManager;
protected readonly ILocalizationService _localizationService;
protected readonly ILocalizedEntityService _localizedEntityService;
protected readonly INotificationService _notificationService;
protected readonly IPictureService _pictureService;
protected readonly IProductService _productService;
protected readonly IStaticCacheManager _staticCacheManager;
protected readonly IStoreMappingService _storeMappingService;
protected readonly ITranslationModelFactory _translationModelFactory;
protected readonly IUrlRecordService _urlRecordService;
protected readonly IWorkContext _workContext;
#endregion
#region Ctor
public CategoryController(ICategoryModelFactory categoryModelFactory,
ICategoryService categoryService,
ICustomerActivityService customerActivityService,
IDiscountService discountService,
IExportManager exportManager,
IImportManager importManager,
ILocalizationService localizationService,
ILocalizedEntityService localizedEntityService,
INotificationService notificationService,
IPictureService pictureService,
IProductService productService,
IStaticCacheManager staticCacheManager,
IStoreMappingService storeMappingService,
ITranslationModelFactory translationModelFactory,
IUrlRecordService urlRecordService,
IWorkContext workContext)
{
_categoryModelFactory = categoryModelFactory;
_categoryService = categoryService;
_customerActivityService = customerActivityService;
_discountService = discountService;
_exportManager = exportManager;
_importManager = importManager;
_localizationService = localizationService;
_localizedEntityService = localizedEntityService;
_notificationService = notificationService;
_pictureService = pictureService;
_productService = productService;
_staticCacheManager = staticCacheManager;
_storeMappingService = storeMappingService;
_translationModelFactory = translationModelFactory;
_urlRecordService = urlRecordService;
_workContext = workContext;
}
#endregion
#region Utilities
protected virtual async Task UpdateLocalesAsync(Category category, CategoryModel model)
{
foreach (var localized in model.Locales)
{
await _localizedEntityService.SaveLocalizedValueAsync(category,
x => x.Name,
localized.Name,
localized.LanguageId);
await _localizedEntityService.SaveLocalizedValueAsync(category,
x => x.Description,
localized.Description,
localized.LanguageId);
await _localizedEntityService.SaveLocalizedValueAsync(category,
x => x.MetaKeywords,
localized.MetaKeywords,
localized.LanguageId);
await _localizedEntityService.SaveLocalizedValueAsync(category,
x => x.MetaDescription,
localized.MetaDescription,
localized.LanguageId);
await _localizedEntityService.SaveLocalizedValueAsync(category,
x => x.MetaTitle,
localized.MetaTitle,
localized.LanguageId);
//search engine name
var seName = await _urlRecordService.ValidateSeNameAsync(category, localized.SeName, localized.Name, false);
await _urlRecordService.SaveSlugAsync(category, seName, localized.LanguageId);
}
}
protected virtual async Task UpdatePictureSeoNamesAsync(Category category)
{
var picture = await _pictureService.GetPictureByIdAsync(category.PictureId);
if (picture != null)
await _pictureService.SetSeoFilenameAsync(picture.Id, await _pictureService.GetPictureSeNameAsync(category.Name));
}
#endregion
#region List
public virtual IActionResult Index()
{
return RedirectToAction("List");
}
[CheckPermission(StandardPermission.Catalog.CATEGORIES_VIEW)]
public virtual async Task<IActionResult> List()
{
//prepare model
var model = await _categoryModelFactory.PrepareCategorySearchModelAsync(new CategorySearchModel());
return View(model);
}
[HttpPost]
[CheckPermission(StandardPermission.Catalog.CATEGORIES_VIEW)]
public virtual async Task<IActionResult> List(CategorySearchModel searchModel)
{
//prepare model
var model = await _categoryModelFactory.PrepareCategoryListModelAsync(searchModel);
return Json(model);
}
#endregion
#region Create / Edit / Delete
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> Create()
{
//prepare model
var model = await _categoryModelFactory.PrepareCategoryModelAsync(new CategoryModel(), null);
return View(model);
}
[HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> Create(CategoryModel model, bool continueEditing)
{
if (ModelState.IsValid)
{
var category = model.ToEntity<Category>();
category.CreatedOnUtc = DateTime.UtcNow;
category.UpdatedOnUtc = DateTime.UtcNow;
await _categoryService.InsertCategoryAsync(category);
//search engine name
model.SeName = await _urlRecordService.ValidateSeNameAsync(category, model.SeName, category.Name, true);
await _urlRecordService.SaveSlugAsync(category, model.SeName, 0);
//locales
await UpdateLocalesAsync(category, model);
//discounts
var allDiscounts = await _discountService.GetAllDiscountsAsync(DiscountType.AssignedToCategories, showHidden: true, isActive: null);
foreach (var discount in allDiscounts)
{
if (model.SelectedDiscountIds != null && model.SelectedDiscountIds.Contains(discount.Id))
await _categoryService.InsertDiscountCategoryMappingAsync(new DiscountCategoryMapping { DiscountId = discount.Id, EntityId = category.Id });
}
await _categoryService.UpdateCategoryAsync(category);
//update picture seo file name
await UpdatePictureSeoNamesAsync(category);
//stores
await _storeMappingService.SaveStoreMappingsAsync(category, model.SelectedStoreIds);
//activity log
await _customerActivityService.InsertActivityAsync("AddNewCategory",
string.Format(await _localizationService.GetResourceAsync("ActivityLog.AddNewCategory"), category.Name), category);
_notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Catalog.Categories.Added"));
if (!continueEditing)
return RedirectToAction("List");
return RedirectToAction("Edit", new { id = category.Id });
}
//prepare model
model = await _categoryModelFactory.PrepareCategoryModelAsync(model, null, true);
//if we got this far, something failed, redisplay form
return View(model);
}
[CheckPermission(StandardPermission.Catalog.CATEGORIES_VIEW)]
public virtual async Task<IActionResult> Edit(int id)
{
//try to get a category with the specified id
var category = await _categoryService.GetCategoryByIdAsync(id);
if (category == null || category.Deleted)
return RedirectToAction("List");
//prepare model
var model = await _categoryModelFactory.PrepareCategoryModelAsync(null, category);
return View(model);
}
[HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> Edit(CategoryModel model, bool continueEditing)
{
//try to get a category with the specified id
var category = await _categoryService.GetCategoryByIdAsync(model.Id);
if (category == null || category.Deleted)
return RedirectToAction("List");
if (ModelState.IsValid)
{
var prevPictureId = category.PictureId;
//if parent category changes, we need to clear cache for previous parent category
if (category.ParentCategoryId != model.ParentCategoryId)
{
await _staticCacheManager.RemoveByPrefixAsync(NopCatalogDefaults.CategoriesByParentCategoryPrefix, category.ParentCategoryId);
await _staticCacheManager.RemoveByPrefixAsync(NopCatalogDefaults.CategoriesChildIdsPrefix, category.ParentCategoryId);
await _staticCacheManager.RemoveByPrefixAsync(NopCatalogDefaults.ChildCategoryIdLookupPrefix);
}
category = model.ToEntity(category);
category.UpdatedOnUtc = DateTime.UtcNow;
await _categoryService.UpdateCategoryAsync(category);
//search engine name
model.SeName = await _urlRecordService.ValidateSeNameAsync(category, model.SeName, category.Name, true);
await _urlRecordService.SaveSlugAsync(category, model.SeName, 0);
//locales
await UpdateLocalesAsync(category, model);
//discounts
var allDiscounts = await _discountService.GetAllDiscountsAsync(DiscountType.AssignedToCategories, showHidden: true, isActive: null);
foreach (var discount in allDiscounts)
{
if (model.SelectedDiscountIds != null && model.SelectedDiscountIds.Contains(discount.Id))
{
//new discount
if (await _categoryService.GetDiscountAppliedToCategoryAsync(category.Id, discount.Id) is null)
await _categoryService.InsertDiscountCategoryMappingAsync(new DiscountCategoryMapping { DiscountId = discount.Id, EntityId = category.Id });
}
else
{
//remove discount
if (await _categoryService.GetDiscountAppliedToCategoryAsync(category.Id, discount.Id) is DiscountCategoryMapping mapping)
await _categoryService.DeleteDiscountCategoryMappingAsync(mapping);
}
}
await _categoryService.UpdateCategoryAsync(category);
//delete an old picture (if deleted or updated)
if (prevPictureId > 0 && prevPictureId != category.PictureId)
{
var prevPicture = await _pictureService.GetPictureByIdAsync(prevPictureId);
if (prevPicture != null)
await _pictureService.DeletePictureAsync(prevPicture);
}
//update picture seo file name
await UpdatePictureSeoNamesAsync(category);
//stores
await _storeMappingService.SaveStoreMappingsAsync(category, model.SelectedStoreIds);
//activity log
await _customerActivityService.InsertActivityAsync("EditCategory",
string.Format(await _localizationService.GetResourceAsync("ActivityLog.EditCategory"), category.Name), category);
_notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Catalog.Categories.Updated"));
if (!continueEditing)
return RedirectToAction("List");
return RedirectToAction("Edit", new { id = category.Id });
}
//prepare model
model = await _categoryModelFactory.PrepareCategoryModelAsync(model, category, true);
//if we got this far, something failed, redisplay form
return View(model);
}
[HttpPost]
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> PreTranslate(int itemId)
{
var translationModel = new TranslationModel();
//try to get a category with the specified id
var category = await _categoryService.GetCategoryByIdAsync(itemId);
if (category == null || category.Deleted)
return Json(translationModel);
//prepare model
var model = await _categoryModelFactory.PrepareCategoryModelAsync(null, category);
translationModel = await _translationModelFactory.PrepareTranslationModelAsync(model,
(nameof(CategoryLocalizedModel.Name), false),
(nameof(CategoryLocalizedModel.Description), true));
return Json(translationModel);
}
[HttpPost]
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> Delete(int id)
{
//try to get a category with the specified id
var category = await _categoryService.GetCategoryByIdAsync(id);
if (category == null)
return RedirectToAction("List");
await _categoryService.DeleteCategoryAsync(category);
//activity log
await _customerActivityService.InsertActivityAsync("DeleteCategory",
string.Format(await _localizationService.GetResourceAsync("ActivityLog.DeleteCategory"), category.Name), category);
_notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Catalog.Categories.Deleted"));
return RedirectToAction("List");
}
[HttpPost]
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> DeleteSelected(ICollection<int> selectedIds)
{
if (selectedIds == null || !selectedIds.Any())
return NoContent();
var categories = await _categoryService.GetCategoriesByIdsAsync(selectedIds.ToArray());
await _categoryService.DeleteCategoriesAsync(categories);
//activity log
var activityLogFormat = await _localizationService.GetResourceAsync("ActivityLog.DeleteCategory");
await _customerActivityService.InsertActivitiesAsync("DeleteCategory", categories, category => string.Format(activityLogFormat, category.Name));
return Json(new { Result = true });
}
#endregion
#region Export / Import
[CheckPermission(StandardPermission.Catalog.CATEGORIES_IMPORT_EXPORT)]
public virtual async Task<IActionResult> ExportXml()
{
try
{
var xml = await _exportManager.ExportCategoriesToXmlAsync();
return File(Encoding.UTF8.GetBytes(xml), "application/xml", "categories.xml");
}
catch (Exception exc)
{
await _notificationService.ErrorNotificationAsync(exc);
return RedirectToAction("List");
}
}
[CheckPermission(StandardPermission.Catalog.CATEGORIES_IMPORT_EXPORT)]
public virtual async Task<IActionResult> ExportXlsx()
{
try
{
var bytes = await _exportManager
.ExportCategoriesToXlsxAsync((await _categoryService.GetAllCategoriesAsync(showHidden: true)).ToList());
return File(bytes, MimeTypes.TextXlsx, "categories.xlsx");
}
catch (Exception exc)
{
await _notificationService.ErrorNotificationAsync(exc);
return RedirectToAction("List");
}
}
[HttpPost]
[CheckPermission(StandardPermission.Catalog.CATEGORIES_IMPORT_EXPORT)]
public virtual async Task<IActionResult> ImportFromXlsx(IFormFile importexcelfile)
{
//a vendor cannot import categories
if (await _workContext.GetCurrentVendorAsync() != null)
return AccessDeniedView();
try
{
if (importexcelfile != null && importexcelfile.Length > 0)
{
await _importManager.ImportCategoriesFromXlsxAsync(importexcelfile.OpenReadStream());
}
else
{
_notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Common.UploadFile"));
return RedirectToAction("List");
}
_notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Catalog.Categories.Imported"));
return RedirectToAction("List");
}
catch (Exception exc)
{
await _notificationService.ErrorNotificationAsync(exc);
return RedirectToAction("List");
}
}
#endregion
#region Products
[HttpPost]
[CheckPermission(StandardPermission.Catalog.CATEGORIES_VIEW)]
public virtual async Task<IActionResult> ProductList(CategoryProductSearchModel searchModel)
{
//try to get a category with the specified id
var category = await _categoryService.GetCategoryByIdAsync(searchModel.CategoryId)
?? throw new ArgumentException("No category found with the specified id");
//prepare model
var model = await _categoryModelFactory.PrepareCategoryProductListModelAsync(searchModel, category);
return Json(model);
}
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> ProductUpdate(CategoryProductModel model)
{
//try to get a product category with the specified id
var productCategory = await _categoryService.GetProductCategoryByIdAsync(model.Id)
?? throw new ArgumentException("No product category mapping found with the specified id");
//fill entity from product
productCategory = model.ToEntity(productCategory);
await _categoryService.UpdateProductCategoryAsync(productCategory);
return new NullJsonResult();
}
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> ProductDelete(int id)
{
//try to get a product category with the specified id
var productCategory = await _categoryService.GetProductCategoryByIdAsync(id)
?? throw new ArgumentException("No product category mapping found with the specified id", nameof(id));
await _categoryService.DeleteProductCategoryAsync(productCategory);
return new NullJsonResult();
}
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> ProductAddPopup(int categoryId)
{
//prepare model
var model = await _categoryModelFactory.PrepareAddProductToCategorySearchModelAsync(new AddProductToCategorySearchModel());
return View(model);
}
[HttpPost]
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> ProductAddPopupList(AddProductToCategorySearchModel searchModel)
{
//prepare model
var model = await _categoryModelFactory.PrepareAddProductToCategoryListModelAsync(searchModel);
return Json(model);
}
[HttpPost]
[FormValueRequired("save")]
[CheckPermission(StandardPermission.Catalog.CATEGORIES_CREATE_EDIT_DELETE)]
public virtual async Task<IActionResult> ProductAddPopup(AddProductToCategoryModel model)
{
//get selected products
var selectedProducts = await _productService.GetProductsByIdsAsync(model.SelectedProductIds.ToArray());
if (selectedProducts.Any())
{
var existingProductCategories = await _categoryService.GetProductCategoriesByCategoryIdAsync(model.CategoryId, showHidden: true);
foreach (var product in selectedProducts)
{
//whether product category with such parameters already exists
if (_categoryService.FindProductCategory(existingProductCategories, product.Id, model.CategoryId) != null)
continue;
//insert the new product category mapping
await _categoryService.InsertProductCategoryAsync(new ProductCategory
{
CategoryId = model.CategoryId,
ProductId = product.Id,
IsFeaturedProduct = false,
DisplayOrder = 1
});
}
}
ViewBag.RefreshPage = true;
return View(new AddProductToCategorySearchModel());
}
#endregion
}