Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
63db66f
If there is any address data for Email, Phone or Fax always display i…
Michael-Herzog Dec 16, 2025
34754de
Fixes the payment widget not displaying when payment methods are sele…
mgesing Dec 17, 2025
4fd0cf5
Adding new addresses wasn't possible because model state wasn't valid…
Michael-Herzog Dec 18, 2025
5c8a6e9
Google reCaptcha (migration) fixes
muratcakir Dec 16, 2025
c2b6238
AI metadata: Async loading pattern
muratcakir Dec 17, 2025
7aee305
AI: Bulletproof, async, fire-forget metadata post-processor
muratcakir Dec 18, 2025
76d4ee6
#1456 Wrong method was being used when marking an order as paid
mgesing Dec 22, 2025
8067387
FilterExpressionParser: Check empty string if term contains wildcard …
muratcakir Jan 9, 2026
15c4154
Stripe: Minor fix for terminal checkout mode
Michael-Herzog Jan 12, 2026
b81bcd5
Captcha: Resource wasn't resolved with "message.Code"
Michael-Herzog Jan 15, 2026
2cef879
Captcha: multiclick event handler prevented captcha event handler fro…
Michael-Herzog Jan 15, 2026
a67252c
Captcha: Respect (jquery unobstrusive) client validation
Michael-Herzog Jan 15, 2026
029aa63
Captcha: Fix for latest commit
Michael-Herzog Jan 15, 2026
bef8dc1
PostFormat may contain Html
Michael-Herzog Jan 7, 2026
507f8d9
Minor submit button fix for login page
Michael-Herzog Jan 19, 2026
2163474
GMC: Added a setting to export products only for those for which GMC …
mgesing Jan 19, 2026
0df1e7e
Fixed "Argument 'languageId' must be greater or less than zero. Value…
mgesing Jan 26, 2026
76b3a6b
Stripe: Fixed bug where the order for which the hook was sent may not…
Michael-Herzog Jan 27, 2026
dbb7ea1
Fixed "Failed to add specification attribute" when assigning a specif…
mgesing Feb 3, 2026
fadfac9
CAPTCHA: Stop event propagation to prevent unobtrusive handler from f…
muratcakir Feb 11, 2026
fae7be9
CAPTCHA DRY: Consolidated foundation and provider code and saved ~400…
muratcakir Feb 11, 2026
30fe495
CAPTCHA: Submit form via requestSubmit vs submit to transfer submit b…
muratcakir Feb 11, 2026
643ca3e
Currency assignment by Country/IP lookup has not respected store res…
Michael-Herzog Feb 17, 2026
a133354
Automatic currency switch should not be performed for system accounts
Michael-Herzog Feb 17, 2026
c938a16
A modified, localized name of a payment method was incorrectly overwr…
mgesing Feb 17, 2026
db0cf2b
Apple Pay must only be rendered on Apple decives
Michael-Herzog Feb 19, 2026
cae3b60
Added namespace (missing due to cherry pick)
Michael-Herzog Feb 19, 2026
7d0c5df
Export the entered and not the calculated price in CSV and XML export
mgesing Feb 23, 2026
08b643b
Enhanced GoogleRecaptchaProvider
muratcakir Feb 25, 2026
29692d0
Enhanced disableSubmitTemporarily
muratcakir Feb 25, 2026
ece2d3c
Fixed order of disableSubmitTemporarily in reCAPTCHA script
muratcakir Feb 25, 2026
d06f733
Heuristic first-gate bot detection
muratcakir Feb 11, 2026
1c2a50f
Updated useragent.yml (better bot/agent detection)
muratcakir Feb 10, 2026
e0fee09
Fix(Identity): Prevent duplicate welcome emails during account activa…
malex1306 Mar 3, 2026
6d188b8
PayPal: Fixed rare bug
Michael-Herzog Mar 9, 2026
b99abe4
fix(admin): include customer and shipping email in order search
malex1306 Mar 13, 2026
9b11c66
Take the customer's preferred shipping method into account when updat…
mgesing Mar 20, 2026
86471f2
no message
mgesing Feb 10, 2026
8027590
Temporarily deactivate OS condition for Windows publish step
muratcakir Apr 21, 2026
025a5d6
Fix OS condition for Windows publish step
muratcakir Apr 21, 2026
7c911fe
Fixes the issue of missing text blocks assigned to products via a rul…
mgesing Apr 27, 2026
c6a0724
Pricing: Apply a product discount if a tier price equals the regular …
mgesing May 15, 2026
66e0e24
Change Docker image repository to thuneck-debug
thuneck-debug Aug 17, 2026
983ec80
Add GitHub Actions workflow for deployment to Plesk
thuneck-debug Aug 17, 2026
f18fdfe
Add GitHub Actions workflow for build and publish
thuneck-debug Aug 17, 2026
357e290
Change branch name in publish workflow
thuneck-debug Aug 17, 2026
a13cefe
Update deploy.yml to include main_shs branch
thuneck-debug Aug 17, 2026
db82a2f
Change publish command to use Smartstore.sln
thuneck-debug Aug 18, 2026
ca9a2d2
Update deploy.yml
thuneck-debug Aug 18, 2026
063ad18
Update deploy workflow for improved publishing
thuneck-debug Aug 18, 2026
ff19255
Update deploy.yml
thuneck-debug Aug 19, 2026
075ac40
Add Serilog.Formatting.Compact.Reader package
thuneck-debug Aug 19, 2026
273d2ba
Update deploy.yml
thuneck-debug Aug 20, 2026
9469c41
Update deploy.yml
thuneck-debug Aug 20, 2026
006b362
Enable dangerous-clean-slate for FTP deployment
thuneck-debug Aug 20, 2026
147559b
Update deploy.yml
thuneck-debug Aug 20, 2026
d50d9e8
Add ProjectReference for Smartstore.Data.SqlServer
thuneck-debug Aug 20, 2026
248cc32
Update deploy.yml
thuneck-debug Aug 20, 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
48 changes: 48 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Build and Deploy Smartstore to Plesk

on:
push:
branches:
- main_shs
workflow_dispatch: # Ermöglicht den manuellen Start per Knopfdruck

jobs:
build-and-deploy:
runs-on: windows-latest

steps:
# 1. Quellcode aus GitHub auschecken
- name: Checkout Source Code
uses: actions/checkout@v4

# 2. .NET SDK bereitstellen
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'

# 3. Projektabhängigkeiten wiederherstellen
- name: Restore dependencies
run: dotnet restore Smartstore.sln

# 4. Smartstore kompilieren (Publish erzeugen)
- name: Publish Smartstore.Web
run: |
dotnet publish src/Smartstore.Web/Smartstore.Web.csproj -c Release -r win-x86 --self-contained false -o ./publish_output /p:CopyLocalLockFileAssemblies=true /p:RazorCompileOnBuild=false /p:PublishModules=true

# 4b. App Offline schalten (Verhindert Datei-Sperren in Plesk und erzwingt Neustart)
- name: Create app_offline.htm
run: |
echo "<h1>Shop wird aktualisiert...</h1>" > ./publish_output/app_offline.htm

# 5. Die fertig kompilierten Dateien per FTP zu Plesk hochladen
# Korrektur: local-dir nutzt jetzt exakt den korrekten Ordner
- name: FTP Deploy to Plesk
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.FTP_SERVER }}
username: 'thuneck'
password: ${{ secrets.FTP_PASSWORD }}
local-dir: ./publish_output/
server-dir: /httpdocs/
# dangerous-clean-slate: true # <--- ZWINGT DIE ACTION ALLES NEU ZU SCHREIBEN!
16 changes: 8 additions & 8 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [debian-latest, windows-latest]
os: [windows-latest]
permissions:
contents: read
packages: write
Expand Down Expand Up @@ -53,17 +53,17 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}

- name: Publish and Push for Linux
if: matrix.os == 'debian-latest'
if: matrix.os == 'ubuntu-latest'
run: |
dotnet publish src/Smartstore.Web/Smartstore.Web.csproj -c Release -o ./.build/release --no-restore --no-build --no-self-contained
docker build --build-arg SOURCE=./.build/release -f NoBuild.Dockerfile -t ghcr.io/smartstore/smartstore-linux:latest -t ghcr.io/smartstore/smartstore-linux:${{ steps.format-branch-label.outputs.branch-label }} .
docker push ghcr.io/smartstore/smartstore-linux:latest
docker push ghcr.io/smartstore/smartstore-linux:${{ steps.format-branch-label.outputs.branch-label }}
docker build --build-arg SOURCE=./.build/release -f NoBuild.Dockerfile -t ghcr.io/thuneck-debug/smartstore-linux:latest -t ghcr.io/thuneck-debug/smartstore-linux:${{ steps.format-branch-label.outputs.branch-label }} .
docker push ghcr.io/thuneck-debug/smartstore-linux:latest
docker push ghcr.io/thuneck-debug/smartstore-linux:${{ steps.format-branch-label.outputs.branch-label }}

- name: Publish and Push for Windows
if: matrix.os == 'windows-latest'
run: |
dotnet publish src/Smartstore.Web/Smartstore.Web.csproj -c Release -o ./.build/release --no-restore --no-build --no-self-contained
docker build --build-arg SOURCE=./.build/release -f Nano.Dockerfile -t ghcr.io/smartstore/smartstore-windows:latest -t ghcr.io/smartstore/smartstore-windows:${{ steps.format-branch-label.outputs.branch-label }} .
docker push ghcr.io/smartstore/smartstore-windows:latest
docker push ghcr.io/smartstore/smartstore-windows:${{ steps.format-branch-label.outputs.branch-label }}
docker build --build-arg SOURCE=./.build/release -f Nano.Dockerfile -t ghcr.io/thuneck-debug/smartstore-windows:latest -t ghcr.io/thuneck-debug/smartstore-windows:${{ steps.format-branch-label.outputs.branch-label }} .
docker push ghcr.io/thuneck-debug/smartstore-windows:latest
docker push ghcr.io/thuneck-debug/smartstore-windows:${{ steps.format-branch-label.outputs.branch-label }}
34 changes: 34 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Build and Publish Smartstore 6.3

on:
push:
branches:
- main_shs # Hier den exakten Namen Ihres Branches eintragen

jobs:
build-and-publish:
runs-on: windows-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x' # Erzwingt .NET 9 für Version 6.3

- name: Restore Dependencies
run: dotnet restore Smartstore.sln

- name: Build and Publish
run: |
dotnet publish Smartstore.sln -c Release -r win-x86 --self-contained false -o ./publish-folder

# Erklaerung: -r win-x86 stellt auf 32-Bit fuer Fritz IT ein. --self-contained false nutzt das Server-.NET-9.

- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: smartstore-plesk-package
path: ./publish-folder
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public async Task CalculateAsync(CalculatorContext context, CalculatorDelegate n
// but exclude additional charge from comparing.
context.FinalPrice -= context.AdditionalCharge;

if (context.MinTierPrice.Value < context.FinalPrice)
// INFO: A tier price may equal the regular price.
if (context.MinTierPrice.Value <= context.FinalPrice)
{
context.FinalPrice = context.MinTierPrice.Value;
context.AppliedTierPrice = tierPriceEntity;
Expand Down
3 changes: 2 additions & 1 deletion src/Smartstore.Core/Catalog/Rules/ProductRuleProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public async Task<bool> MatchesAsync(
.BuildFacetMap(false)
.CheckSpelling(0);

searchQuery.CustomData["RuleCheckProductId"] = productId;
searchQuery = group.ApplyFilters(searchQuery);

var productQuery = _catalogSearchService.PrepareQuery(searchQuery);
Expand Down Expand Up @@ -211,7 +212,7 @@ CatalogSearchQuery categoryFilter(SearchFilterContext ctx, int[] categoryIds)
{
if (!categoryIds.IsNullOrEmpty())
{
var featuredOnly = _catalogSettings.IncludeFeaturedProductsInNormalLists ? (bool?)null : false;
bool? featuredOnly = (_catalogSettings.IncludeFeaturedProductsInNormalLists || ctx.Query.CustomData.ContainsKey("RuleCheckProductId")) ? null : false;

if (_catalogSettings.ShowProductsFromSubcategories)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1109,7 +1109,13 @@ protected virtual async Task<CartShipping> GetAdjustedShippingTotalAsync(Shoppin
var response = await _shippingService.GetShippingOptionsAsync(cart, customer.ShippingAddress, null, cart.StoreId);
if (response.Success && response.ShippingOptions.Count > 0)
{
shipping.Option = response.ShippingOptions[0];
var preferredMethodId = customer.GenericAttributes?.PreferredShippingOption?.ShippingMethodId ?? 0;
if (preferredMethodId != 0)
{
shipping.Option = response.ShippingOptions.FirstOrDefault(x => x.ShippingMethodId == preferredMethodId);
}

shipping.Option ??= response.ShippingOptions[0];
}
}

Expand Down
1 change: 0 additions & 1 deletion src/Smartstore.Core/Content/Media/MediaService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ public async Task<int> CountFilesAsync(MediaSearchQuery query)

public async Task<FileCountResult> CountFilesGroupedAsync(MediaFilesFilter filter)
{
// TODO: (core) Throws
Guard.NotNull(filter);

// Base db query
Expand Down
6 changes: 6 additions & 0 deletions src/Smartstore.Core/Platform/AI/Metadata/AIMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public class AIMetadata
/// </summary>
public AIModelCollection Models { get; set; } = default!;

/// <summary>
/// A flag indicating whether this instance is a post-processed clone.
/// </summary>
[JsonIgnore]
public bool PostProcessed { get; set; }

#endregion

#region Supports...
Expand Down
16 changes: 15 additions & 1 deletion src/Smartstore.Core/Platform/AI/Metadata/IAIMetadataLoader.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Smartstore.Core.AI.Metadata
#nullable enable

namespace Smartstore.Core.AI.Metadata
{
/// <summary>
/// Loads and caches AI metadata.
Expand All @@ -11,5 +13,17 @@ public interface IAIMetadataLoader
/// <param name="moduleSystemName">The AI modules's system name to load metadata.json from.</param>"
/// <exception cref="InvalidOperationException"></exception>
AIMetadata LoadMetadata(string moduleSystemName);

/// <summary>
/// Performs post-processing operations asynchronously using the specified AI metadata.
/// </summary>
/// <param name="localMetadata">The metadata object containing information required for post-processing. Cannot be null.</param>
/// <returns>Return null if no post-processing was performed. Return an <see cref="AIMetadata"/> instance to update the metadata cache deferredly.</returns>
Task<AIMetadata?> PostProcessAsync(AIMetadata localMetadata);

/// <summary>
/// Replaces the cached metadata for the given <paramref name="moduleSystemName"/> with the given <paramref name="metadata"/> instance.
/// </summary>
void ReplaceMetadata(string moduleSystemName, AIMetadata metadata);
}
}
34 changes: 30 additions & 4 deletions src/Smartstore.Core/Platform/AI/Metadata/JsonAIMetadataLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Smartstore.Caching;
using Smartstore.ComponentModel;

namespace Smartstore.Core.AI.Metadata
Expand All @@ -27,11 +28,19 @@ public JsonAIMetadataLoader(IMemoryCache cache, IApplicationContext appContext)
};
}

protected static string BuildCacheKey(string moduleSystemName)
=> "aimetadata:" + moduleSystemName;

protected IMemoryCache Cache
{
get => _cache;
}

public AIMetadata LoadMetadata(string moduleSystemName)
{
Guard.NotEmpty(moduleSystemName);

var cacheKey = "aimetadata:" + moduleSystemName;
var cacheKey = BuildCacheKey(moduleSystemName);

var result = _cache.GetOrCreate(cacheKey, entry =>
{
Expand All @@ -42,10 +51,27 @@ public AIMetadata LoadMetadata(string moduleSystemName)
entry.AddExpirationToken(changeToken);
}

return metadata;
return new CacheEntry { Key = cacheKey, Value = metadata, ValueType = typeof(AIMetadata) };
});

return result!;
return (AIMetadata)result!.Value;
}

public virtual Task<AIMetadata?> PostProcessAsync(AIMetadata localMetadata)
{
localMetadata.PostProcessed = true;
return Task.FromResult<AIMetadata?>(null);
}

public void ReplaceMetadata(string moduleSystemName, AIMetadata metadata)
{
Guard.NotEmpty(moduleSystemName);
Guard.NotNull(metadata);

if (_cache.TryGetValue(BuildCacheKey(moduleSystemName), out CacheEntry? entry))
{
entry!.Value = metadata;
}
}

protected virtual (AIMetadata, IChangeToken?) LoadMetadataCore(string moduleSystemName)
Expand Down Expand Up @@ -77,7 +103,7 @@ protected virtual (AIMetadata, IChangeToken?) LoadMetadataCore(string moduleSyst

public void Invalidate(string moduleSystemName)
{
_cache.Remove("aimetadata:" + moduleSystemName);
_cache.Remove(BuildCacheKey(moduleSystemName));
}
}
}
77 changes: 76 additions & 1 deletion src/Smartstore.Core/Platform/AI/Services/AIProviderBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Smartstore.Core.Content.Media;
using Smartstore.Core.Localization;
using Smartstore.IO;
using Smartstore.Threading;
using Smartstore.Utilities;

namespace Smartstore.Core.AI
Expand All @@ -28,11 +29,85 @@ public abstract class AIProviderBase : IAIProvider
/// </summary>
protected const string ContinueReason = "length";

private readonly IAIMetadataLoader _metadataLoader;
private readonly string _moduleSystemName;
private AIMetadata _metadata;

public AIProviderBase(IAIMetadataLoader metadataLoader, string moduleSystemName)
{
_metadataLoader = metadataLoader;
_moduleSystemName = moduleSystemName;
}

public Localizer T { get; set; } = NullLocalizer.Instance;

public abstract bool IsActive();

public virtual AIMetadata Metadata { get; protected set; }
public AIMetadata Metadata
{
get
{
if (_metadata == null)
{
_metadata = _metadataLoader.LoadMetadata(_moduleSystemName);

// Ensure that metadata is post-processed only once.
if (!_metadata.PostProcessed)
{
// Post-process metadata asynchronously (fire-and-forget).
var lockHandle = AsyncLock.Keyed("aimetadatalock:" + _metadata.ProviderId);
// Reload from cache
var metadata = _metadataLoader.LoadMetadata(_moduleSystemName);
if (!metadata.PostProcessed)
{
PostProcessMetadataInBackground(metadata, lockHandle);
}
else
{
lockHandle.Release();
}
}
}

return _metadata;
}
protected set => _metadata = value;
}

private void PostProcessMetadataInBackground(AIMetadata metadata, ILockHandle lockHandle)
{
var task = _metadataLoader.PostProcessAsync(metadata);

if (task.IsCompleted)
{
// If already completed, update metadata immediately.
if (task.Status == TaskStatus.RanToCompletion && task.Result != null)
{
_metadata = task.Result;
}

_metadata.PostProcessed = true;
lockHandle.Release();
}
else
{
// Update cached metadata when post-processing is done.
task.ContinueWith(t =>
{
if (t.Result != null)
{
t.Result.PostProcessed = true;
_metadataLoader.ReplaceMetadata(_moduleSystemName, t.Result);
}
else
{
_metadata.PostProcessed = true;
}

lockHandle.Release();
});
}
}

public virtual AIModelCollection GetModels(AIChatTopic topic)
{
Expand Down
Loading