Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
tatarincev committed Mar 18, 2016
2 parents 54c9e2a + e16fe3b commit 2073fc7
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 89 deletions.
2 changes: 1 addition & 1 deletion PLATFORM/GlobalAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
[assembly: AssemblyCompany("VirtoCommerce")]
[assembly: AssemblyProduct("Virto Commerce 2.9")]
[assembly: AssemblyCopyright("Copyright © VirtoCommerce 2011-2016")]
[assembly: AssemblyFileVersion("2.9.1321.0")]
[assembly: AssemblyFileVersion("2.9.1323.0")]
[assembly: AssemblyVersion("2.9.0.0")]
[assembly: AssemblyInformationalVersion("2.9")]

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Virto Commerce 2.x
| Branch | Status |
| ------------- | ------------- |
| [Master - releases](https://github.com/VirtoCommerce/vc-community) | [![Build Status](http://ci.virtocommerce.com:8080/buildStatus/icon?job=VirtoCommerce 2.x CI Build - master)](http://ci.virtocommerce.com:8080/job/VirtoCommerce 2.x CI Build - master) |
| [Development / Integration](https://github.com/VirtoCommerce/vc-community/tree/dev) | [![Build Status](http://ci.virtocommerce.com:8080/buildStatus/icon?job=VirtoCommerce 2.x CI Build)](http://ci.virtocommerce.com:8080/job/VirtoCommerce 2.x CI Build) |
| [Development / Integration](https://github.com/VirtoCommerce/vc-community/tree/dev) | [![Build Status](http://ci.virtocommerce.com:8080/buildStatus/icon?job=VirtoCommerce 2.x CI Build - dev)](http://ci.virtocommerce.com:8080/job/VirtoCommerce 2.x CI Build - dev) |
| Feature (on going feature dev) | [![Build Status](http://ci.virtocommerce.com:8080/buildStatus/icon?job=VirtoCommerce 2.x CI Build - branches)](http://ci.virtocommerce.com:8080/job/VirtoCommerce 2.x CI Build - branches) |

Virto Commerce is the second generation release and is the only enterprise level e-commerce product fully available under Open Source license. Virto Commerce is based on .NET 4.5 with extensive use of MVC, IoC, EF, Azure, Angular JS and many other cutting edge technologies. It can be deployed in Microsoft Cloud (Azure), Amazon Web Services (AWS) and on-premise. Mobile App Starter built using Ionic Framework is also available.
Expand Down
12 changes: 6 additions & 6 deletions STOREFRONT/VirtoCommerce.Storefront/App_Start/RouteConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace VirtoCommerce.Storefront
{
public class RouteConfig
{

public static void RegisterRoutes(RouteCollection routes, Func<WorkContext> workContextFactory, ICommerceCoreModuleApi commerceCoreApi, IStaticContentService staticContentService, ICacheManager<object> cacheManager)
{
routes.IgnoreRoute("favicon.ico");
Expand Down Expand Up @@ -56,7 +56,7 @@ public static void RegisterRoutes(RouteCollection routes, Func<WorkContext> work
routes.MapLocalizedStorefrontRoute("API.Marketing.GetDynamicContent", "storefrontapi/marketing/dynamiccontent/{placeName}", defaults: new { controller = "ApiMarketing", action = "GetDynamicContent" });
//Account API
routes.MapLocalizedStorefrontRoute("API.Account.GetCurrentCustomer", "storefrontapi/account", defaults: new { controller = "ApiAccount", action = "GetCurrentCustomer" });

//Quote requests API
routes.MapLocalizedStorefrontRoute("API.QuoteRequest.GetItemsCount", "storefrontapi/quoterequests/{number}/itemscount", defaults: new { controller = "ApiQuoteRequest", action = "GetItemsCount" });
routes.MapLocalizedStorefrontRoute("API.QuoteRequest.Get", "storefrontapi/quoterequests/{number}", defaults: new { controller = "ApiQuoteRequest", action = "Get" });
Expand Down Expand Up @@ -104,7 +104,7 @@ public static void RegisterRoutes(RouteCollection routes, Func<WorkContext> work
routes.MapLocalizedStorefrontRoute("ShopifyCart.UpdateJs", "cart/update.js", defaults: new { controller = "ShopifyCompatibility", action = "UpdateJs" });

// QuoteRequest

routes.MapLocalizedStorefrontRoute("QuoteRequest.CurrentQuoteRequest", "quoterequest", defaults: new { controller = "QuoteRequest", action = "CurrentQuoteRequest" });
routes.MapLocalizedStorefrontRoute("Account.QuoteRequests", "account/quoterequests", defaults: new { controller = "QuoteRequest", action = "QuoteRequests" });
routes.MapLocalizedStorefrontRoute("Account.QuoteRequestByNumber", "quoterequest/{number}", defaults: new { controller = "QuoteRequest", action = "QuoteRequestByNumber" });
Expand All @@ -119,7 +119,7 @@ public static void RegisterRoutes(RouteCollection routes, Func<WorkContext> work
routes.MapLocalizedStorefrontRoute("Common.NoStore", "common/nostore", defaults: new { controller = "Common", action = "NoStore" });
routes.MapLocalizedStorefrontRoute("Common.Maintenance", "maintenance", defaults: new { controller = "Common", action = "Maintenance" });


//Product routes
routes.MapLocalizedStorefrontRoute("Product.GetProduct", "product/{productId}", defaults: new { controller = "Product", action = "ProductDetails" });
routes.MapLocalizedStorefrontRoute("Product.GetProductJson", "product/{productId}/json", defaults: new { controller = "Product", action = "ProductDetailsJson" });
Expand All @@ -132,9 +132,9 @@ public static void RegisterRoutes(RouteCollection routes, Func<WorkContext> work
routes.MapLocalizedStorefrontRoute("Blogs.GetBlog", "blogs/{blog}", defaults: new { controller = "Blog", action = "GetBlog" });
routes.MapLocalizedStorefrontRoute("Blogs.GetBlogArticle", "blogs/{blog}/{article}", defaults: new { controller = "Blog", action = "GetBlogArticle" });

Func<string, Route> seoRouteFactory = url => new SeoRoute(url, new MvcRouteHandler(), workContextFactory, commerceCoreApi, staticContentService, cacheManager);
Func<string, Route> seoRouteFactory = url => new SeoRoute(url, new MvcRouteHandler(), workContextFactory, commerceCoreApi, cacheManager);
routes.MapLocalizedStorefrontRoute(name: "SeoRoute", url: "{*path}", defaults: new { controller = "StorefrontHome", action = "Index" }, constraints: null, routeFactory: seoRouteFactory);

}
}
}
171 changes: 90 additions & 81 deletions STOREFRONT/VirtoCommerce.Storefront/Routing/SeoRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
using VirtoCommerce.Client.Model;
using VirtoCommerce.Storefront.Common;
using VirtoCommerce.Storefront.Model;
using VirtoCommerce.Storefront.Model.Catalog;
using VirtoCommerce.Storefront.Model.Common;
using VirtoCommerce.Storefront.Model.Services;
using VirtoCommerce.Storefront.Model.StaticContent;

namespace VirtoCommerce.Storefront.Routing
Expand All @@ -19,15 +17,13 @@ public class SeoRoute : Route
{
private readonly Func<WorkContext> _workContextFactory;
private readonly ICommerceCoreModuleApi _commerceCoreApi;
private readonly IStaticContentService _contentService;
private readonly ICacheManager<object> _cacheManager;

public SeoRoute(string url, IRouteHandler routeHandler, Func<WorkContext> workContextFactory, ICommerceCoreModuleApi commerceCoreApi, IStaticContentService staticContentService, ICacheManager<object> cacheManager)
public SeoRoute(string url, IRouteHandler routeHandler, Func<WorkContext> workContextFactory, ICommerceCoreModuleApi commerceCoreApi, ICacheManager<object> cacheManager)
: base(url, routeHandler)
{
_workContextFactory = workContextFactory;
_commerceCoreApi = commerceCoreApi;
_contentService = staticContentService;
_cacheManager = cacheManager;
}

Expand All @@ -43,108 +39,124 @@ public override RouteData GetRouteData(HttpContextBase httpContext)
var path = data.Values["path"] as string;
var store = data.Values["store"] as string;
//Special workaround for case when url contains only slug without store (one store case)
if(string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(store) && workContext.AllStores != null)
if (string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(store) && workContext.AllStores != null)
{
//use {store} as {path} if not exist any store with name {store}
path = workContext.AllStores.Any(x => string.Equals(store, x.Id, StringComparison.InvariantCultureIgnoreCase)) ? null : store;
//use {store} as {path} if not exist any store with name {store}
path = workContext.AllStores.Any(x => string.Equals(store, x.Id, StringComparison.InvariantCultureIgnoreCase)) ? null : store;
}
//Get all seo records for requested slug and also all other seo records with different slug and languages but related to same object
// GetSeoRecords('A') returns
// { objectType: 'Product', objectId: '1', SemanticUrl: 'A', Language: 'en-us', active : false }
// { objectType: 'Product', objectId: '1', SemanticUrl: 'AA', Language: 'en-us', active : true }
var seoRecords = GetSeoRecords(path);
var seoRecord = seoRecords.Where(x => path.Equals(x.SemanticUrl, StringComparison.OrdinalIgnoreCase))
.FindBestSeoMatch(workContext.CurrentLanguage, workContext.CurrentStore);

if (seoRecord != null)
if (seoRecords != null)
{
// Ensure the slug is active
if (seoRecord.IsActive == null || !seoRecord.IsActive.Value)
var seoRecord = seoRecords
.Where(x => string.Equals(path, x.SemanticUrl, StringComparison.OrdinalIgnoreCase))
.FindBestSeoMatch(workContext.CurrentLanguage, workContext.CurrentStore);

if (seoRecord != null)
{
// Slug is not active. Try to find the active one for the same entity and language.
seoRecord = seoRecords.Where(x=>x.ObjectType == seoRecord.ObjectType && x.ObjectId == seoRecord.ObjectId && x.IsActive != null && x.IsActive.Value)
.FindBestSeoMatch(workContext.CurrentLanguage, workContext.CurrentStore);

if (seoRecord == null)
// Ensure the slug is active
if (seoRecord.IsActive == null || !seoRecord.IsActive.Value)
{
// No active slug found
data.Values["controller"] = "Error";
data.Values["action"] = "Http404";
// Slug is not active. Try to find the active one for the same entity and language.
seoRecord = seoRecords.Where(x => x.ObjectType == seoRecord.ObjectType && x.ObjectId == seoRecord.ObjectId && x.IsActive != null && x.IsActive.Value)
.FindBestSeoMatch(workContext.CurrentLanguage, workContext.CurrentStore);

if (seoRecord == null)
{
// No active slug found
data.Values["controller"] = "Error";
data.Values["action"] = "Http404";
}
else
{
// The active slug is found
var response = httpContext.Response;
response.Status = "301 Moved Permanently";
response.RedirectLocation = string.Format("{0}{1}", workContext.CurrentStore.Url, seoRecord.SemanticUrl);
response.End();
data = null;
}
}
else
{
// The active slug is found
var response = httpContext.Response;
response.Status = "301 Moved Permanently";
response.RedirectLocation = string.Format("{0}{1}", workContext.CurrentStore.Url, seoRecord.SemanticUrl);
response.End();
data = null;
// Redirect to the slug for the current language if it differs from the requested slug
var actualActiveSeoRecord = seoRecords.Where(x => x.ObjectType == seoRecord.ObjectType && x.ObjectId == seoRecord.ObjectId && x.IsActive != null && x.IsActive.Value)
.FindBestSeoMatch(workContext.CurrentLanguage, workContext.CurrentStore);
//If actual seo different that requested need redirect 302
if (!string.Equals(actualActiveSeoRecord.SemanticUrl, seoRecord.SemanticUrl, StringComparison.OrdinalIgnoreCase))
{
var response = httpContext.Response;
response.Status = "302 Moved Temporarily";
response.RedirectLocation = string.Concat(workContext.CurrentStore.Url, actualActiveSeoRecord.SemanticUrl);
response.End();
data = null;
}
else
{
// Process the URL
switch (seoRecord.ObjectType)
{
case "CatalogProduct":
data.Values["controller"] = "Product";
data.Values["action"] = "ProductDetails";
data.Values["productId"] = seoRecord.ObjectId;
break;
case "Category":
data.Values["controller"] = "CatalogSearch";
data.Values["action"] = "CategoryBrowsing";
data.Values["categoryId"] = seoRecord.ObjectId;
break;
}
}
}
}
else
else if (!string.IsNullOrEmpty(path))
{
// Redirect to the slug for the current language if it differs from the requested slug
var actualActiveSeoRecord = seoRecords.Where(x => x.ObjectType == seoRecord.ObjectType && x.ObjectId == seoRecord.ObjectId && x.IsActive != null && x.IsActive.Value)
.FindBestSeoMatch(workContext.CurrentLanguage, workContext.CurrentStore);
//If actual seo different that requested need redirect 302
if (!string.Equals(actualActiveSeoRecord.SemanticUrl, seoRecord.SemanticUrl, StringComparison.OrdinalIgnoreCase))
var contentPage = TryToFindContentPageWithUrl(workContext, path);
if (contentPage != null)
{
var response = httpContext.Response;
response.Status = "302 Moved Temporarily";
response.RedirectLocation = string.Format("{0}{1}", workContext.CurrentStore.Url, actualActiveSeoRecord.SemanticUrl);
response.End();
data = null;
data.Values["controller"] = "Page";
data.Values["action"] = "GetContentPage";
data.Values["page"] = contentPage;
}
else
{
// Process the URL
switch (seoRecord.ObjectType)
{
case "CatalogProduct":
data.Values["controller"] = "Product";
data.Values["action"] = "ProductDetails";
data.Values["productId"] = seoRecord.ObjectId;
break;
case "Category":
data.Values["controller"] = "CatalogSearch";
data.Values["action"] = "CategoryBrowsing";
data.Values["categoryId"] = seoRecord.ObjectId;
break;
}
data.Values["controller"] = "Error";
data.Values["action"] = "Http404";
}
}
}
else if(!String.IsNullOrEmpty(path))
{
var contentPage = TryToFindContentPageWithUrl(workContext, path);
if(contentPage != null)
{
data.Values["controller"] = "Page";
data.Values["action"] = "GetContentPage";
data.Values["page"] = contentPage;
}
else
{
data.Values["controller"] = "Error";
data.Values["action"] = "Http404";
}
}
}

return data;
}

private ContentItem TryToFindContentPageWithUrl(WorkContext workContext, string url)

private static ContentItem TryToFindContentPageWithUrl(WorkContext workContext, string url)
{
url = url.TrimStart('/');
var pages = workContext.Pages.Where(x => string.Equals(x.Permalink, url, StringComparison.CurrentCultureIgnoreCase) || string.Equals(x.Url, url, StringComparison.InvariantCultureIgnoreCase));
//Need return page with current or invariant language
var retVal = pages.FirstOrDefault(x => x.Language == workContext.CurrentLanguage);
if(retVal == null)
ContentItem result = null;

if (workContext.Pages != null)
{
retVal = pages.FirstOrDefault(x => x.Language.IsInvariant);
url = url.TrimStart('/');
var pages = workContext.Pages
.Where(x =>
string.Equals(x.Permalink, url, StringComparison.CurrentCultureIgnoreCase) ||
string.Equals(x.Url, url, StringComparison.InvariantCultureIgnoreCase))
.ToList();

// Return page with current language or invariant language
result = pages.FirstOrDefault(x => x.Language == workContext.CurrentLanguage);
if (result == null)
{
result = pages.FirstOrDefault(x => x.Language.IsInvariant);
}
}
return retVal;

return result;
}

private List<VirtoCommerceDomainCommerceModelSeoInfo> GetSeoRecords(string path)
Expand All @@ -156,16 +168,13 @@ private List<VirtoCommerceDomainCommerceModelSeoInfo> GetSeoRecords(string path)
var tokens = path.Split('/');
// TODO: Store path tokens as breadcrumbs to the work context
var slug = tokens.LastOrDefault();
if (!String.IsNullOrEmpty(slug))
if (!string.IsNullOrEmpty(slug))
{
seoRecords = _cacheManager.Get(string.Join(":", "CommerceGetSeoInfoBySlug", slug), "ApiRegion", () => { return _commerceCoreApi.CommerceGetSeoInfoBySlug(slug); });
seoRecords = _cacheManager.Get(string.Join(":", "CommerceGetSeoInfoBySlug", slug), "ApiRegion", () => _commerceCoreApi.CommerceGetSeoInfoBySlug(slug));
}
}

return seoRecords;
}



}
}

0 comments on commit 2073fc7

Please sign in to comment.