Skip to content

Commit

Permalink
Merge branch 'release/6.24.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
vc-ci committed Jul 10, 2023
2 parents c7db101 + 1748185 commit 26343ce
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Authors>VirtoCommerce</Authors>
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>6.23.0</VersionPrefix>
<VersionPrefix>6.24.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
<VersionSuffix Condition=" '$(VersionSuffix)' != '' AND '$(BuildNumber)' != '' ">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
</PropertyGroup>
Expand Down
3 changes: 3 additions & 0 deletions VirtoCommerce.Storefront.Model/ISeoInfoService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using VirtoCommerce.Storefront.Model.StaticContent;
using VirtoCommerce.Storefront.Model.Stores;

namespace VirtoCommerce.Storefront.Model
Expand All @@ -8,5 +9,7 @@ public interface ISeoInfoService
Task<SeoInfo[]> GetSeoInfosBySlug(string slug);

Task<SeoInfo[]> GetBestMatchingSeoInfos(string slug, Store store, string currentCulture);

ContentItem GetContentItem(string slug, WorkContext context);
}
}
9 changes: 9 additions & 0 deletions VirtoCommerce.Storefront.Model/ISpaRouteService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading.Tasks;

namespace VirtoCommerce.Storefront.Model
{
public interface ISpaRouteService
{
Task<bool> IsSpaRoute(string route);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ public async Task<SlugInfoResult> GetInfoBySlugAsync(string slug, [FromQuery] st
);

var page = pages.FirstOrDefault(x => x.Language.CultureName.EqualsInvariant(culture))
?? pages.FirstOrDefault(x => x.Language.IsInvariant)
?? pages.FirstOrDefault(x => x.AliasesUrls.Contains(pageUrl, StringComparer.OrdinalIgnoreCase));
?? pages.FirstOrDefault(x => x.Language.IsInvariant)
?? pages.FirstOrDefault(x => x.AliasesUrls.Contains(pageUrl, StringComparer.OrdinalIgnoreCase));
result.ContentItem = page;

}
Expand Down
68 changes: 61 additions & 7 deletions VirtoCommerce.Storefront/Controllers/ErrorController.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,70 @@
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using VirtoCommerce.Storefront.Infrastructure;
using VirtoCommerce.Storefront.Model;
using VirtoCommerce.Storefront.Model.Common;
using VirtoCommerce.Storefront.Model.Common.Exceptions;

namespace VirtoCommerce.Storefront.Controllers
{
[StorefrontRoute("error")]
public class ErrorController : Controller
public class ErrorController : StorefrontControllerBase
{
private readonly IWorkContextAccessor _workContextAccessor;
public ErrorController(IWorkContextAccessor workContextAccessor)
private readonly ISeoInfoService _seoInfoService;
private readonly ISpaRouteService _spaRouteService;

public ErrorController(
IWorkContextAccessor workContextAccessor,
IStorefrontUrlBuilder urlBuilder,
ISeoInfoService seoInfoService,
ISpaRouteService spaRouteService)
: base(workContextAccessor, urlBuilder)
{
_workContextAccessor = workContextAccessor;
_seoInfoService = seoInfoService;
_spaRouteService = spaRouteService;
}

[Route("{errCode}")]
public IActionResult Error(int? errCode)
public async Task<IActionResult> Error(int? errCode)
{
//Returns index page on 404 error when the store.IsSpa flag is activated
if (errCode == StatusCodes.Status404NotFound && _workContextAccessor.WorkContext.CurrentStore.IsSpa)
if (errCode == StatusCodes.Status404NotFound && WorkContext.CurrentStore.IsSpa)
{
Response.StatusCode = StatusCodes.Status200OK;
var path = TrimTwoLetterLangSegment(Request.HttpContext.Features.Get<IStatusCodeReExecuteFeature>()?.OriginalPath);
Response.StatusCode = StatusCodes.Status404NotFound;

if (path == "/")
{
Response.StatusCode = StatusCodes.Status200OK;
return View("index");
}

if (string.IsNullOrEmpty(path))
{
return View("index");
}

var slug = path.Split('/').Last();
if (!string.IsNullOrEmpty(slug))
{
var seoInfos = await _seoInfoService.GetBestMatchingSeoInfos(slug, WorkContext.CurrentStore, WorkContext.CurrentLanguage.CultureName);
Response.StatusCode = seoInfos.Any() ? StatusCodes.Status200OK : StatusCodes.Status404NotFound;
}

if (Response.StatusCode == StatusCodes.Status404NotFound)
{
Response.StatusCode = _seoInfoService.GetContentItem($"/{slug}", WorkContext) != null ? StatusCodes.Status200OK : StatusCodes.Status404NotFound;
}

if (Response.StatusCode == StatusCodes.Status404NotFound)
{
Response.StatusCode = await _spaRouteService.IsSpaRoute(path) ? StatusCodes.Status200OK : StatusCodes.Status404NotFound;
}

return View("index");
}
var exceptionFeature = base.HttpContext.Features.Get<IExceptionHandlerFeature>();
Expand All @@ -43,5 +85,17 @@ public IActionResult AccessDenied()
Response.StatusCode = StatusCodes.Status403Forbidden;
return View("AccessDenied");
}

private string TrimTwoLetterLangSegment(string path)
{
var language = WorkContext.CurrentStore.Languages.FirstOrDefault(x => Regex.IsMatch(path, @"^/\b" + x.TwoLetterLanguageName + @"\b/", RegexOptions.IgnoreCase));

if (language != null)
{
path = Regex.Replace(path, @"/\b" + language.TwoLetterLanguageName + @"\b/", "/", RegexOptions.IgnoreCase);
}

return path;
}
}
}
61 changes: 61 additions & 0 deletions VirtoCommerce.Storefront/Domain/SeoInfoService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using VirtoCommerce.Storefront.AutoRestClients.CoreModuleApi;
using VirtoCommerce.Storefront.Common;
using VirtoCommerce.Storefront.Model;
using VirtoCommerce.Storefront.Model.Common;
using VirtoCommerce.Storefront.Model.StaticContent;
using VirtoCommerce.Storefront.Model.Stores;

namespace VirtoCommerce.Storefront.Domain
{
public class SeoInfoService : ISeoInfoService
{
private readonly ICommerce _coreModuleApi;

public SeoInfoService(ICommerce coreModuleApi)
{
_coreModuleApi = coreModuleApi;
}

public async Task<SeoInfo[]> GetSeoInfosBySlug(string slug)
{
var result = (await _coreModuleApi.GetSeoInfoBySlugAsync(slug)).Select(x => x.ToSeoInfo()).ToArray();

return result;
}

public async Task<SeoInfo[]> GetBestMatchingSeoInfos(string slug, Store store, string currentCulture)
{
var result = (await _coreModuleApi.GetSeoInfoBySlugAsync(slug)).GetBestMatchingSeoInfos(store, currentCulture, slug).Select(x => x.ToSeoInfo()).ToArray();

return result;
}

public ContentItem GetContentItem(string slug, WorkContext context)
{
ContentItem result = null;
var pageUrl = slug == "__index__home__page__" ? "/" : $"/{slug}";
try
{
var pages = context.Pages.Where(p =>
string.Equals(p.Url, pageUrl, StringComparison.OrdinalIgnoreCase)
|| string.Equals(p.Url, slug, StringComparison.OrdinalIgnoreCase)
);

var page = pages.FirstOrDefault(x => x.Language.CultureName.EqualsInvariant(context.CurrentLanguage.CultureName))
?? pages.FirstOrDefault(x => x.Language.IsInvariant)
?? pages.FirstOrDefault(x => x.AliasesUrls.Contains(pageUrl, StringComparer.OrdinalIgnoreCase));
result = page;

}
catch
{
//do nothing
}

return result;
}
}
}
32 changes: 0 additions & 32 deletions VirtoCommerce.Storefront/Domain/SeoInfoServise.cs

This file was deleted.

79 changes: 79 additions & 0 deletions VirtoCommerce.Storefront/Domain/SpaRouteService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;
using VirtoCommerce.Storefront.Model;
using VirtoCommerce.Storefront.Model.Caching;
using VirtoCommerce.Storefront.Model.Common;
using VirtoCommerce.Storefront.Model.Common.Caching;
using VirtoCommerce.Storefront.Model.StaticContent;

namespace VirtoCommerce.Storefront.Domain
{
public class SpaRouteService : ISpaRouteService
{
private readonly IContentBlobProvider _contentBlobProvider;
private readonly IStorefrontMemoryCache _memoryCache;
private readonly IWorkContextAccessor _workContextAccessor;

public SpaRouteService(
IContentBlobProvider contentBlobProvider,
IStorefrontMemoryCache memoryCache,
IWorkContextAccessor workContextAccessor)
{
_contentBlobProvider = contentBlobProvider;
_memoryCache = memoryCache;
_workContextAccessor = workContextAccessor;
}

public virtual async Task<bool> IsSpaRoute(string route)
{
var workContext = _workContextAccessor.WorkContext;
var cacheKey = CacheKey.With(GetType(), nameof(IsSpaRoute), workContext.CurrentStore.Id, route);
var result = await _memoryCache.GetOrCreateExclusiveAsync(cacheKey, async (_) =>
{
var routes = await GetSpaRoutes();

var isSpaRoute = routes.Any(jsPattern =>
{
// Input sample: jsPattern = "/^\\/account\\/profile\\/?$/i"
// Only the char "i" can be an ending. The others chars are not used
// when generating RegExp patterns in the `routes.json` file.
var options = jsPattern.EndsWith("i") ? RegexOptions.IgnoreCase : RegexOptions.None;
var pattern = Regex.Replace(jsPattern, @"^\/|\/i?$", string.Empty);

return Regex.IsMatch(route, pattern, options);
});

return isSpaRoute;
});

return result;
}

protected virtual async Task<List<string>> GetSpaRoutes()
{
var workContext = _workContextAccessor.WorkContext;
var cacheKey = CacheKey.With(GetType(), nameof(GetSpaRoutes), workContext.CurrentStore.Id);
var routes = await _memoryCache.GetOrCreateExclusiveAsync(cacheKey, async (_) =>
{
var result = new List<string>();
var currentThemeName = !string.IsNullOrEmpty(workContext.CurrentStore.ThemeName) ? workContext.CurrentStore.ThemeName : "default";
var currentThemePath = Path.Combine("Themes", workContext.CurrentStore.Id, currentThemeName);
var currentThemeSettingPath = Path.Combine(currentThemePath, "config", "routes.json");

if (_contentBlobProvider.PathExists(currentThemeSettingPath))
{
await using var stream = _contentBlobProvider.OpenRead(currentThemeSettingPath);
result = JsonConvert.DeserializeObject<List<string>>(await stream.ReadToStringAsync());
}

return result;
});

return routes;
}
}
}
3 changes: 2 additions & 1 deletion VirtoCommerce.Storefront/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<ICurrencyService, CurrencyService>();
services.AddSingleton<ISlugRouteService, SlugRouteService>();
services.AddSingleton<IMemberService, MemberService>();
services.AddSingleton<ISeoInfoService, SeoInfoServise>();
services.AddSingleton<ISeoInfoService, SeoInfoService>();
services.AddSingleton<ISpaRouteService, SpaRouteService>();

services.AddSingleton<IStaticContentService, StaticContentService>();
services.AddSingleton<IMenuLinkListService, MenuLinkListServiceImpl>();
Expand Down

0 comments on commit 26343ce

Please sign in to comment.