Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1f517b5
Add MessageTemplate management for Store area (controller + CRUD views)
Copilot May 12, 2026
100ba4b
Fix implicit global variable in MessageTemplate partial view
Copilot May 12, 2026
c81b017
Prevent duplicate message templates per store in Create action
Copilot May 13, 2026
081fb18
Potential fix for pull request finding 'Generic catch clause'
KrzysztofPajak May 13, 2026
e720b1a
Add missing resource string for duplicate template name validation
Copilot May 13, 2026
f4681db
Block copy when template with same name already exists for current store
Copilot May 13, 2026
54f4377
Fix false positive duplicate check when copying global message templates
Copilot May 13, 2026
74c5fa1
Add missing resource strings for message template tabs (store/global)
Copilot May 13, 2026
fb220b8
Block copy of store-specific templates (LimitedToStores=true) in Copy…
Copilot May 13, 2026
5195dd8
Fix duplicate copy check and add missing admin.content.messagetemplat…
Copilot May 14, 2026
d90c92a
Optimize duplicate check in message template copy
KrzysztofPajak May 14, 2026
d3f4a12
Potential fix for pull request finding
KrzysztofPajak May 14, 2026
3cd28d8
Add server-side pagination to ListGlobal and ListStore grid actions
Copilot May 14, 2026
2668755
Fix false-positive duplicate check in Create action to allow store-sp…
Copilot May 14, 2026
09a5d27
Simplify ListStore to use GetAllMessageTemplates("") with explicit st…
Copilot May 17, 2026
71c0a6c
Improve GetAllMessageTemplates: remove cache, add keywords search and…
Copilot May 17, 2026
4cb89b4
Fix case-insensitive keywords search in GetAllMessageTemplates
Copilot May 17, 2026
60a6373
Extend GetAllMessageTemplates keyword search to match Name or Subject
Copilot May 17, 2026
cc37fc2
Fix admin MessageTemplate List grid pagination controls
Copilot May 17, 2026
d9f4c6d
Add edit link to global list Name column; show global templates read-…
Copilot May 17, 2026
58447a3
Extract isReadOnly variable in Edit.cshtml to avoid duplication
Copilot May 17, 2026
e8a74ed
Refactor string comparison in MessageTemplateService
KrzysztofPajak May 17, 2026
ea374dd
Add pageSize to Kendo UI grid configuration
KrzysztofPajak May 17, 2026
18ef6ea
Update package versions and project references
KrzysztofPajak May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 34 additions & 34 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,69 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.4.0" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.5.0" />
<PackageVersion Include="ExcelMapper" Version="6.0.641" />
<PackageVersion Include="Microsoft.AspNetCore.Http" Version="2.3.9" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Http" Version="2.3.10" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.8" />
<PackageVersion Include="Microsoft.FeatureManagement.AspNetCore" Version="4.5.0" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.14.5" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.14.14" />
<PackageVersion Include="Scryber.Core" Version="9.5.0" />
<PackageVersion Include="Scryber.Core.OpenType" Version="9.2.0" />
<PackageVersion Include="MailKit" Version="4.16.0" />
<PackageVersion Include="AWSSDK.Core" Version="4.0.4" />
<PackageVersion Include="AWSSDK.S3" Version="4.0.22.1" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.27.0" />
<PackageVersion Include="AWSSDK.Core" Version="4.0.7.1" />
<PackageVersion Include="AWSSDK.S3" Version="4.0.23.3" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.28.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageVersion Include="SkiaSharp" Version="3.119.2" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.2" />
<PackageVersion Include="LiteDB" Version="5.0.21" />
<PackageVersion Include="MongoDB.Bson" Version="3.8.0" />
<PackageVersion Include="MongoDB.Driver" Version="3.8.0" />
<PackageVersion Include="MongoDB.Bson" Version="3.8.1" />
<PackageVersion Include="MongoDB.Driver" Version="3.8.1" />
<PackageVersion Include="DotLiquid" Version="2.3.197" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
<PackageVersion Include="GoogleAuthenticator" Version="3.2.0" />
<PackageVersion Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="8.5.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.8" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.3.0" />
<PackageVersion Include="MediatR" Version="12.5.0" />
<PackageVersion Include="Scrutor" Version="7.0.0" />
<PackageVersion Include="StackExchange.Redis" Version="2.12.14" />
<PackageVersion Include="StackExchange.Redis" Version="2.13.1" />
<PackageVersion Include="FluentValidation" Version="12.1.1" />
<PackageVersion Include="Microsoft.AspNetCore.JsonPatch.SystemTextJson" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Facebook" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.JsonPatch.SystemTextJson" Version="10.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Facebook" Version="10.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="10.0.8" />
<PackageVersion Include="Braintree" Version="5.39.0" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.7" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.8" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.7.2" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.7" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.8" />
<PackageVersion Include="System.Xml.XPath.XmlDocument" Version="4.7.0" />
<PackageVersion Include="Stripe.net" Version="51.1.0" />
<PackageVersion Include="elFinder.Net.AspNetCore" Version="1.5.0" />
<PackageVersion Include="elFinder.Net.Core" Version="1.5.0" />
<PackageVersion Include="elFinder.Net.Drivers.FileSystem" Version="1.5.0" />
<PackageVersion Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.5.1" />
<PackageVersion Include="Azure.Extensions.AspNetCore.DataProtection.Keys" Version="1.6.1" />
<PackageVersion Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.5.3" />
<PackageVersion Include="Azure.Extensions.AspNetCore.DataProtection.Keys" Version="1.6.3" />
<PackageVersion Include="Azure.Identity" Version="1.21.0" />
<PackageVersion Include="Flurl.Http" Version="4.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="10.0.8" />
<PackageVersion Include="NetEscapades.AspNetCore.SecurityHeaders" Version="1.3.1" />
<PackageVersion Include="Wangkanai.Detection" Version="8.20.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.7" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.8" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="MSTest.TestAdapter" Version="4.2.1" />
<PackageVersion Include="MSTest.TestFramework" Version="4.2.1" />
<PackageVersion Include="MSTest.TestAdapter" Version="4.2.3" />
<PackageVersion Include="MSTest.TestFramework" Version="4.2.3" />
<PackageVersion Include="coverlet.collector" Version="10.0.0" />
<PackageVersion Include="NUnit" Version="4.5.1" />
<PackageVersion Include="Verify.MSTest" Version="31.16.2" />
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.2.4" />
<PackageVersion Include="Aspire.Hosting.MongoDB" Version="13.2.4" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.5.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.5.0" />
<PackageVersion Include="NUnit" Version="4.6.0" />
<PackageVersion Include="Verify.MSTest" Version="31.16.3" />
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.3.3" />
<PackageVersion Include="Aspire.Hosting.MongoDB" Version="13.3.3" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.6.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.6.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
Expand All @@ -75,7 +75,7 @@
<PackageVersion Include="Aspire.Hosting.Orchestration.$(NETCoreSdkRuntimeIdentifier)" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<PackageVersion Update="Aspire.Dashboard.Sdk.win-x64" Version="13.2.4" />
<PackageVersion Update="Aspire.Hosting.Orchestration.win-x64" Version="13.2.4" />
<PackageVersion Update="Aspire.Dashboard.Sdk.win-x64" Version="13.3.3" />
<PackageVersion Update="Aspire.Hosting.Orchestration.win-x64" Version="13.3.3" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Grand.Domain.Messages;
using Grand.Domain;
using Grand.Domain.Messages;

namespace Grand.Business.Core.Interfaces.Messages;

Expand Down Expand Up @@ -44,8 +45,11 @@ public interface IMessageTemplateService
/// Gets all message templates
/// </summary>
/// <param name="storeId">Store identifier; pass "" to load all records</param>
/// <returns>Message template list</returns>
Task<IList<MessageTemplate>> GetAllMessageTemplates(string storeId);
/// <param name="keywords">Keywords to filter by name or subject; pass "" to skip</param>
/// <param name="pageIndex">Page index (0-based)</param>
/// <param name="pageSize">Page size</param>
/// <returns>Paged list of message templates</returns>
Task<IPagedList<MessageTemplate>> GetAllMessageTemplates(string storeId, string keywords = "", int pageIndex = 0, int pageSize = int.MaxValue);

/// <summary>
/// Create a copy of message template with all depended data
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Grand.Business.Core.Interfaces.Common.Security;
using Grand.Business.Core.Interfaces.Messages;
using Grand.Data;
using Grand.Domain;
using Grand.Domain.Messages;
using Grand.Infrastructure.Caching;
using Grand.Infrastructure.Caching.Constants;
Expand Down Expand Up @@ -136,28 +137,31 @@ public virtual async Task<MessageTemplate> GetMessageTemplateByName(string messa
/// Gets all message templates
/// </summary>
/// <param name="storeId">Store identifier; pass "" to load all records</param>
/// <returns>Message template list</returns>
public virtual async Task<IList<MessageTemplate>> GetAllMessageTemplates(string storeId)
/// <param name="keywords">Keywords to filter by name or subject; pass "" to skip</param>
/// <param name="pageIndex">Page index (0-based)</param>
/// <param name="pageSize">Page size</param>
/// <returns>Paged list of message templates</returns>
public virtual async Task<IPagedList<MessageTemplate>> GetAllMessageTemplates(string storeId, string keywords = "", int pageIndex = 0, int pageSize = int.MaxValue)
{
var key = string.Format(CacheKey.MESSAGETEMPLATES_ALL_KEY, storeId);
return await _cacheBase.GetAsync(key, async () =>
{
var query = from p in _messageTemplateRepository.Table
select p;

query = query.OrderBy(t => t.Name);

//Store acl
if (string.IsNullOrEmpty(storeId) || _accessControlConfig.IgnoreStoreLimitations)
return await Task.FromResult(query.ToList());
var query = from p in _messageTemplateRepository.Table
select p;

//Store acl
if (!string.IsNullOrEmpty(storeId) && !_accessControlConfig.IgnoreStoreLimitations)
query = from p in query
where !p.LimitedToStores || p.Stores.Contains(storeId)
select p;
query = query.OrderBy(t => t.Name);

return await Task.FromResult(query.ToList());
});
if (!string.IsNullOrEmpty(keywords))
{
var lowerKeywords = keywords.ToLower();
query = query.Where(t => t.Name.ToLower().Contains(lowerKeywords) ||
(t.Subject != null && t.Subject.Contains(lowerKeywords, StringComparison.CurrentCultureIgnoreCase)));
}

query = query.OrderBy(t => t.Name);

return await PagedList<MessageTemplate>.Create(query, pageIndex, pageSize);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

<Target Name="CopyFile" AfterTargets="AfterBuild">
<ItemGroup>
<CopyFiles Include="$(NuGetPackageRoot)\microsoft.aspnetcore.authentication.facebook\10.0.7\lib\net10.0\*.dll" />
<CopyFiles Include="$(NuGetPackageRoot)\microsoft.aspnetcore.authentication.facebook\10.0.8\lib\net10.0\*.dll" />
</ItemGroup>
<Copy SourceFiles="@(CopyFiles)" DestinationFolder="..\..\Web\Grand.Web\Plugins\Authentication.Facebook\" />
</Target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

<Target Name="CopyFile" AfterTargets="AfterBuild">
<ItemGroup>
<CopyFiles Include="$(NuGetPackageRoot)\microsoft.aspnetcore.authentication.google\10.0.7\lib\net10.0\*.dll" />
<CopyFiles Include="$(NuGetPackageRoot)\microsoft.aspnetcore.authentication.google\10.0.8\lib\net10.0\*.dll" />
</ItemGroup>
<Copy SourceFiles="@(CopyFiles)" DestinationFolder="..\..\Web\Grand.Web\Plugins\Authentication.Google\" />
</Target>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@model MessageTemplateListModel
@inject AdminAreaSettings adminAreaSettings
@{
//page title
ViewBag.Title = Loc["Admin.Content.MessageTemplates"];
Expand Down Expand Up @@ -75,15 +76,14 @@
// Cancel the changes
this.cancelChanges();
},
pageSize: @(adminAreaSettings.DefaultGridPageSize),
serverPaging: true,
serverFiltering: true,
serverSorting: true
},
pageable: {
refresh: true,
numeric: false,
previousNext: false,
info: false
pageSizes: [@(adminAreaSettings.GridPageSizes)]
},
editable: {
confirmation: false,
Expand Down
12 changes: 6 additions & 6 deletions src/Web/Grand.Web.Admin/Controllers/MessageTemplateController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ public async Task<IActionResult> List()
[HttpPost]
public async Task<IActionResult> List(DataSourceRequest command, MessageTemplateListModel model)
{
var messageTemplates = await _messageTemplateService.GetAllMessageTemplates(model.SearchStoreId);
var messageTemplates = await _messageTemplateService.GetAllMessageTemplates(
model.SearchStoreId,
keywords: model.Name,
pageIndex: command.Page - 1,
pageSize: command.PageSize);

if (!string.IsNullOrEmpty(model.Name))
messageTemplates = messageTemplates.Where
(x => x.Name.ToLowerInvariant().Contains(model.Name.ToLowerInvariant()) ||
x.Subject.ToLowerInvariant().Contains(model.Name.ToLowerInvariant())).ToList();
var items = new List<MessageTemplateModel>();
foreach (var x in messageTemplates)
{
Expand All @@ -103,7 +103,7 @@ public async Task<IActionResult> List(DataSourceRequest command, MessageTemplate

var gridModel = new DataSourceResult {
Data = items,
Total = messageTemplates.Count
Total = messageTemplates.TotalCount
};

return Json(gridModel);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@model MessageTemplateModel
@{
ViewBag.Title = Loc["Admin.Content.MessageTemplates.AddNew"];
Layout = Constants.LayoutStore;
}
<form asp-area="@Constants.AreaStore" asp-controller="MessageTemplate" asp-action="Create" method="post">

<div class="row">
<div class="col-md-12">
<div class="x_panel light form-fit">
<div class="x_title">
<div class="caption">
<i class="fa fa-envelope-o"></i>
@Loc["Admin.Content.MessageTemplates.AddNew"]
<small>
<i class="fa fa-arrow-circle-left"></i>@Html.ActionLink(Loc["Admin.Content.MessageTemplates.BackToList"], "List")
</small>
</div>
<div class="actions">
<div class="btn-group btn-group-devided">
<button class="btn btn-success" type="submit" name="save">
<i class="fa fa-check"></i> @Loc["Admin.Common.Save"]
</button>
<button class="btn btn-success" type="submit" name="save-continue">
<i class="fa fa-check-circle"></i> @Loc["Admin.Common.SaveContinue"]
</button>
</div>
</div>
</div>
<div class="x_content form">
<partial name="Partials/CreateOrUpdate" model="Model"/>
</div>
</div>
</div>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@model MessageTemplateModel
@{
ViewBag.Title = Loc["Admin.Content.MessageTemplates.EditMessageTemplateDetails"];
Layout = Constants.LayoutStore;
var isReadOnly = (bool)(ViewBag.IsReadOnly ?? false);
}
<form asp-area="@Constants.AreaStore" asp-controller="MessageTemplate" asp-action="Edit" method="post">

<div class="row">
<div class="col-md-12">
<div class="x_panel light form-fit">
<div class="x_title">
<div class="caption">
<i class="fa fa-envelope-o"></i>
@Loc["Admin.Content.MessageTemplates.EditMessageTemplateDetails"] - @Model.Name
<small>
<i class="fa fa-arrow-circle-left"></i>@Html.ActionLink(Loc["Admin.Content.MessageTemplates.BackToList"], "List")
</small>
</div>
<div class="actions">
<div class="btn-group btn-group-devided">
Comment thread
KrzysztofPajak marked this conversation as resolved.
@if (!isReadOnly)
{
<button class="btn btn-success" type="submit" name="save">
<i class="fa fa-check"></i> @Loc["Admin.Common.Save"]
</button>
<button class="btn btn-success" type="submit" name="save-continue">
<i class="fa fa-check-circle"></i> @Loc["Admin.Common.SaveContinue"]
</button>
<input type="submit" name="message-template-copy" asp-action="CopyTemplate" class="btn blue" value="@Loc["Admin.Content.MessageTemplates.Copy"]" onclick="return confirm('@Loc["Admin.Common.AreYouSure"]');"/>
<span id="message-template-delete" class="btn red">
<i class="fa fa-trash-o"></i> @Loc["Admin.Common.Delete"]
</span>
}
</div>
</div>
</div>
<div class="x_content form">
<partial name="Partials/CreateOrUpdate" model="Model"/>
</div>
</div>
</div>
</div>
</form>
@if (!isReadOnly)
{
<admin-delete-confirmation button-id="message-template-delete"/>
}
Loading
Loading