diff --git a/Geta.Optimizely.Sitemaps.sln b/Geta.Optimizely.Sitemaps.sln index 10c75700..a12f7f3c 100644 --- a/Geta.Optimizely.Sitemaps.sln +++ b/Geta.Optimizely.Sitemaps.sln @@ -7,9 +7,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sandbox", "Sandbox", "{9003 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geta.Optimizely.Sitemaps", "src\Geta.Optimizely.Sitemaps\Geta.Optimizely.Sitemaps.csproj", "{A56D25DD-73FB-4754-B054-C5CD9B52804F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Geta.Optimizely.Sitemaps.Commerce", "src\Geta.Optimizely.Sitemaps.Commerce\Geta.Optimizely.Sitemaps.Commerce.csproj", "{39B5430D-35AF-4413-980B-1CE51B367DC7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geta.Optimizely.Sitemaps.Commerce", "src\Geta.Optimizely.Sitemaps.Commerce\Geta.Optimizely.Sitemaps.Commerce.csproj", "{39B5430D-35AF-4413-980B-1CE51B367DC7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Foundation", "sandbox\Foundation\src\Foundation\Foundation.csproj", "{82A14BA5-4A85-4DC3-833E-37EBC47BB891}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundation", "sandbox\Foundation\src\Foundation\Foundation.csproj", "{82A14BA5-4A85-4DC3-833E-37EBC47BB891}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/README.md b/README.md index 72b88638..c650eb39 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ This tool allows you to generate xml sitemaps for search engines to better index - ability to include pages that are in a different branch than the one of the start page - ability to generate sitemaps for mobile pages - it also supports multi-site and multi-language environments +- ability to augment URL generation See the [editor guide](docs/editor-guide.md) for more information. @@ -64,6 +65,19 @@ And for the Commerce support add a call to: services.AddSitemapsCommerce(); ``` +In order to augment Urls for a given set of content one must prepare to build a service that identifies content to be augmented +and yields augmented Uris from IUriAugmenterService.GetAugmentUris(IContent content, CurrentLanguageContent languageContentInfo, Uri fullUri) method. + +1. [Create a service that implements IUriAugmenterService yielding multiple Uris per single input content/language/Uri.](sandbox/Foundation/src/Foundation/Infrastructure/Cms/Services/SitemapUriParameterAugmenterService.cs). +2. Ensure the services is set, overring the default service, within the optionsAction of AddSitemaps. For example: + +```csharp +services.AddSitemaps(options => +{ + options.SetAugmenterService(); +}); +``` + It is also possible to configure the application in `appsettings.json` file. A configuration from the `appsettings.json` will override configuration configured in Startup. Below is an `appsettings.json` configuration example. ```json diff --git a/sandbox/Foundation/src/Foundation/Infrastructure/Cms/Services/SitemapUriParameterAugmenterService.cs b/sandbox/Foundation/src/Foundation/Infrastructure/Cms/Services/SitemapUriParameterAugmenterService.cs new file mode 100644 index 00000000..130bdec0 --- /dev/null +++ b/sandbox/Foundation/src/Foundation/Infrastructure/Cms/Services/SitemapUriParameterAugmenterService.cs @@ -0,0 +1,54 @@ +using EPiServer; +using EPiServer.Core; +using EPiServer.DataAbstraction; +using Foundation.Features.People.PersonItemPage; +using Geta.Optimizely.Sitemaps; +using Geta.Optimizely.Sitemaps.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Foundation.Infrastructure.Cms.Services +{ + public class SitemapUriParameterAugmenterService : IUriAugmenterService + { + private readonly IContentTypeRepository _contentTypeRepository; + private readonly IContentModelUsage _contentModelUsage; + private readonly IContentRepository _contentRepository; + + public SitemapUriParameterAugmenterService(IContentTypeRepository contentTypeRepository, IContentModelUsage contentModelUsage, IContentRepository contentRepository) + { + _contentTypeRepository = contentTypeRepository; + _contentModelUsage = contentModelUsage; + _contentRepository = contentRepository; + } + + public IEnumerable GetAugmentUris(IContent content, CurrentLanguageContent languageContentInfo, Uri fullUri) + { + if (content is PageData pageContent) + { + if (pageContent.PageTypeName == nameof(Features.People.PersonListPage)) + { + var fullUriString = fullUri.ToString(); + + var personPageType = _contentTypeRepository.Load(); + var usages = _contentModelUsage.ListContentOfContentType(personPageType).Select(c => _contentRepository.Get(c.ContentLink)); + // Group all of the results by the querystring parameters that drive the page. + var nameSectorLocations = usages.GroupBy(k => new { k.Name, k.Sector, k.Location }); + + // Enumerate the total set of expected name/sectors/locations in ordr for them to be indexed. + foreach (var nameSectorLocation in nameSectorLocations) + { + var augmentedUri = new Uri($"{fullUriString}?name={nameSectorLocation.Key.Name}§or={nameSectorLocation.Key.Sector}&location={nameSectorLocation.Key.Location}"); + yield return augmentedUri; + } + } + else + { + yield return fullUri; + } + } + } + } +} diff --git a/sandbox/Foundation/src/Foundation/Startup.cs b/sandbox/Foundation/src/Foundation/Startup.cs index 8d42709e..1ac6d0db 100644 --- a/sandbox/Foundation/src/Foundation/Startup.cs +++ b/sandbox/Foundation/src/Foundation/Startup.cs @@ -1,9 +1,12 @@ -using EPiServer.Authorization; +using EPiServer; +using EPiServer.Authorization; using EPiServer.ContentApi.Cms; using EPiServer.ContentApi.Cms.Internal; using EPiServer.ContentDefinitionsApi; using EPiServer.ContentManagementApi; +using EPiServer.Core; using EPiServer.Data; +using EPiServer.DataAbstraction; using EPiServer.Framework.Web.Resources; using EPiServer.Labs.ContentManager; using EPiServer.OpenIDConnect; @@ -15,6 +18,7 @@ using Foundation.Infrastructure; using Foundation.Infrastructure.Cms.Extensions; using Foundation.Infrastructure.Cms.ModelBinders; +using Foundation.Infrastructure.Cms.Services; using Foundation.Infrastructure.Cms.Users; using Foundation.Infrastructure.Display; using Geta.NotFoundHandler.Infrastructure.Configuration; @@ -22,6 +26,7 @@ using Geta.NotFoundHandler.Optimizely; using Geta.Optimizely.Sitemaps; using Geta.Optimizely.Sitemaps.Commerce; +using Geta.Optimizely.Sitemaps.Services; using Jhoose.Security.DependencyInjection; using Mediachase.Commerce.Anonymous; using Mediachase.Commerce.Orders; @@ -91,7 +96,11 @@ public void ConfigureServices(IServiceCollection services) services.AddDetection(); services.AddTinyMceConfiguration(); - services.AddSitemaps(); + // Implement the UriAugmenterServiceImplementationFactory in order to enumerate the PersonalListPage querystring parameters. + services.AddSitemaps(options => + { + options.SetAugmenterService(); + }); services.AddSitemapsCommerce(); //site specific diff --git a/src/Geta.Optimizely.Sitemaps.Commerce/CommerceAndStandardSitemapXmlGenerator.cs b/src/Geta.Optimizely.Sitemaps.Commerce/CommerceAndStandardSitemapXmlGenerator.cs index 3c9e01c6..3df4974a 100644 --- a/src/Geta.Optimizely.Sitemaps.Commerce/CommerceAndStandardSitemapXmlGenerator.cs +++ b/src/Geta.Optimizely.Sitemaps.Commerce/CommerceAndStandardSitemapXmlGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Geta Digital. All rights reserved. +// Copyright (c) Geta Digital. All rights reserved. // Licensed under Apache-2.0. See the LICENSE file in the project root for more information using System.Collections.Generic; @@ -10,6 +10,7 @@ using EPiServer.Web; using EPiServer.Web.Routing; using Geta.Optimizely.Sitemaps.Repositories; +using Geta.Optimizely.Sitemaps.Services; using Geta.Optimizely.Sitemaps.Utils; using Geta.Optimizely.Sitemaps.XML; using Mediachase.Commerce.Catalog; @@ -33,6 +34,7 @@ public CommerceAndStandardSitemapXmlGenerator( ILanguageBranchRepository languageBranchRepository, ReferenceConverter referenceConverter, IContentFilter contentFilter, + IUriAugmenterService uriAugmenterService, ISynchronizedObjectInstanceCache objectCache, IMemoryCache memoryCache, ILogger logger) @@ -44,6 +46,7 @@ public CommerceAndStandardSitemapXmlGenerator( languageBranchRepository, referenceConverter, contentFilter, + uriAugmenterService, objectCache, memoryCache, logger) diff --git a/src/Geta.Optimizely.Sitemaps.Commerce/CommerceSitemapXmlGenerator.cs b/src/Geta.Optimizely.Sitemaps.Commerce/CommerceSitemapXmlGenerator.cs index 8e61ec42..2d8063bf 100644 --- a/src/Geta.Optimizely.Sitemaps.Commerce/CommerceSitemapXmlGenerator.cs +++ b/src/Geta.Optimizely.Sitemaps.Commerce/CommerceSitemapXmlGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Geta Digital. All rights reserved. +// Copyright (c) Geta Digital. All rights reserved. // Licensed under Apache-2.0. See the LICENSE file in the project root for more information using System; @@ -12,6 +12,7 @@ using EPiServer.Web; using EPiServer.Web.Routing; using Geta.Optimizely.Sitemaps.Repositories; +using Geta.Optimizely.Sitemaps.Services; using Geta.Optimizely.Sitemaps.Utils; using Geta.Optimizely.Sitemaps.XML; using Mediachase.Commerce.Catalog; @@ -36,6 +37,7 @@ public CommerceSitemapXmlGenerator( ILanguageBranchRepository languageBranchRepository, ReferenceConverter referenceConverter, IContentFilter contentFilter, + IUriAugmenterService uriAugmenterService, ISynchronizedObjectInstanceCache objectCache, IMemoryCache memoryCache, ILogger logger) @@ -46,6 +48,7 @@ public CommerceSitemapXmlGenerator( siteDefinitionRepository, languageBranchRepository, contentFilter, + uriAugmenterService, objectCache, memoryCache, logger) @@ -65,7 +68,7 @@ protected override IEnumerable GetSitemapXmlElements() }; } - var descendants = ContentRepository.GetDescendents(rootContentReference).ToList(); + var descendants = ContentRepository.GetDescendents(rootContentReference); return GenerateXmlElements(descendants); } diff --git a/src/Geta.Optimizely.Sitemaps/Configuration/SitemapOptions.cs b/src/Geta.Optimizely.Sitemaps/Configuration/SitemapOptions.cs index 4eef86ff..336b0026 100644 --- a/src/Geta.Optimizely.Sitemaps/Configuration/SitemapOptions.cs +++ b/src/Geta.Optimizely.Sitemaps/Configuration/SitemapOptions.cs @@ -1,9 +1,19 @@ -namespace Geta.Optimizely.Sitemaps.Configuration +using System; +using Geta.Optimizely.Sitemaps.Services; + +namespace Geta.Optimizely.Sitemaps.Configuration { public class SitemapOptions { public bool EnableRealtimeSitemap { get; set; } = false; public bool EnableRealtimeCaching { get; set; } = true; public bool EnableLanguageDropDownInAdmin { get; set; } = false; + + public Type UriAugmenterService { get; set; } = typeof(DefaultUriAugmenterService); + + public void SetAugmenterService() where T : class, IUriAugmenterService + { + UriAugmenterService = typeof(T); + } } -} \ No newline at end of file +} diff --git a/src/Geta.Optimizely.Sitemaps/ServiceCollectionExtensions.cs b/src/Geta.Optimizely.Sitemaps/ServiceCollectionExtensions.cs index 69671c34..c4056930 100644 --- a/src/Geta.Optimizely.Sitemaps/ServiceCollectionExtensions.cs +++ b/src/Geta.Optimizely.Sitemaps/ServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using EPiServer.Authorization; using EPiServer.Shell.Modules; @@ -7,6 +7,7 @@ using Geta.Optimizely.Sitemaps.Entities; using Geta.Optimizely.Sitemaps.Models; using Geta.Optimizely.Sitemaps.Repositories; +using Geta.Optimizely.Sitemaps.Services; using Geta.Optimizely.Sitemaps.Utils; using Geta.Optimizely.Sitemaps.XML; using Microsoft.AspNetCore.Authorization; @@ -23,7 +24,7 @@ public static IServiceCollection AddSitemaps(this IServiceCollection services) { return AddSitemaps(services, _ => { }, DefaultPolicy); } - + public static IServiceCollection AddSitemaps( this IServiceCollection services, Action setupAction) @@ -53,6 +54,10 @@ public static IServiceCollection AddSitemaps( configuration.GetSection("Geta:Sitemaps").Bind(options); }); + var options = new SitemapOptions(); + setupAction(options); + services.AddSingleton(typeof(IUriAugmenterService), options.UriAugmenterService); + services.AddAuthorization(options => { options.AddPolicy(Constants.PolicyName, configurePolicy); diff --git a/src/Geta.Optimizely.Sitemaps/Services/DefaultUriAugmenterService.cs b/src/Geta.Optimizely.Sitemaps/Services/DefaultUriAugmenterService.cs new file mode 100644 index 00000000..abfeb84f --- /dev/null +++ b/src/Geta.Optimizely.Sitemaps/Services/DefaultUriAugmenterService.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using EPiServer.Core; +using EPiServer.ServiceLocation; + +namespace Geta.Optimizely.Sitemaps.Services +{ + public class DefaultUriAugmenterService : IUriAugmenterService + { + public IEnumerable GetAugmentUris(IContent content, CurrentLanguageContent languageContentInfo, Uri fullUri) + { + yield return fullUri; + } + } +} diff --git a/src/Geta.Optimizely.Sitemaps/Services/IUriAugmenterService.cs b/src/Geta.Optimizely.Sitemaps/Services/IUriAugmenterService.cs new file mode 100644 index 00000000..38757963 --- /dev/null +++ b/src/Geta.Optimizely.Sitemaps/Services/IUriAugmenterService.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using EPiServer.Core; + +namespace Geta.Optimizely.Sitemaps.Services +{ + public interface IUriAugmenterService + { + /// + /// Allows sitemap implementer an easy facility to take a simple url and expand it in a number of ways, includig parameterizing it with QueryStrings. + /// + /// Original content of page URL being created + /// Language for URI + /// Origin URI to be included in sitemap + /// Must include origin to be included in sitemap + IEnumerable GetAugmentUris(IContent content, CurrentLanguageContent languageContentInfo, Uri originUri); + } +} diff --git a/src/Geta.Optimizely.Sitemaps/XML/MobileSitemapXmlGenerator.cs b/src/Geta.Optimizely.Sitemaps/XML/MobileSitemapXmlGenerator.cs index fd56a091..54210727 100644 --- a/src/Geta.Optimizely.Sitemaps/XML/MobileSitemapXmlGenerator.cs +++ b/src/Geta.Optimizely.Sitemaps/XML/MobileSitemapXmlGenerator.cs @@ -9,6 +9,7 @@ using EPiServer.Web; using EPiServer.Web.Routing; using Geta.Optimizely.Sitemaps.Repositories; +using Geta.Optimizely.Sitemaps.Services; using Geta.Optimizely.Sitemaps.Utils; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; @@ -24,6 +25,7 @@ public MobileSitemapXmlGenerator( ISiteDefinitionRepository siteDefinitionRepository, ILanguageBranchRepository languageBranchRepository, IContentFilter contentFilter, + IUriAugmenterService uriAugmenterService, ISynchronizedObjectInstanceCache objectCache, IMemoryCache cache, ILogger logger) @@ -34,6 +36,7 @@ public MobileSitemapXmlGenerator( siteDefinitionRepository, languageBranchRepository, contentFilter, + uriAugmenterService, objectCache, cache, logger) diff --git a/src/Geta.Optimizely.Sitemaps/XML/SitemapXmlGenerator.cs b/src/Geta.Optimizely.Sitemaps/XML/SitemapXmlGenerator.cs index 10d2243d..f58e07c3 100644 --- a/src/Geta.Optimizely.Sitemaps/XML/SitemapXmlGenerator.cs +++ b/src/Geta.Optimizely.Sitemaps/XML/SitemapXmlGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Geta Digital. All rights reserved. +// Copyright (c) Geta Digital. All rights reserved. // Licensed under Apache-2.0. See the LICENSE file in the project root for more information using System; @@ -19,6 +19,7 @@ using Geta.Optimizely.Sitemaps.Entities; using Geta.Optimizely.Sitemaps.Models; using Geta.Optimizely.Sitemaps.Repositories; +using Geta.Optimizely.Sitemaps.Services; using Geta.Optimizely.Sitemaps.SpecializedProperties; using Geta.Optimizely.Sitemaps.Utils; using Microsoft.Extensions.Caching.Memory; @@ -41,6 +42,7 @@ public abstract class SitemapXmlGenerator : ISitemapXmlGenerator protected readonly ISiteDefinitionRepository SiteDefinitionRepository; protected readonly ILanguageBranchRepository LanguageBranchRepository; protected readonly IContentFilter ContentFilter; + private readonly IUriAugmenterService _uriAugmenterService; private readonly ISynchronizedObjectInstanceCache _objectCache; private readonly IMemoryCache _memoryCache; private readonly ILogger _logger; @@ -55,6 +57,8 @@ public abstract class SitemapXmlGenerator : ISitemapXmlGenerator public bool IsDebugMode { get; set; } + private readonly Regex _dashRegex = new Regex("[-]+", RegexOptions.Compiled); + protected SitemapXmlGenerator( ISitemapRepository sitemapRepository, IContentRepository contentRepository, @@ -62,6 +66,7 @@ protected SitemapXmlGenerator( ISiteDefinitionRepository siteDefinitionRepository, ILanguageBranchRepository languageBranchRepository, IContentFilter contentFilter, + IUriAugmenterService uriAugmenterService, ISynchronizedObjectInstanceCache objectCache, IMemoryCache memoryCache, ILogger logger) @@ -74,6 +79,7 @@ protected SitemapXmlGenerator( EnabledLanguages = LanguageBranchRepository.ListEnabled(); UrlSet = new HashSet(); ContentFilter = contentFilter; + _uriAugmenterService = uriAugmenterService; _objectCache = objectCache; _memoryCache = memoryCache; _logger = logger; @@ -192,6 +198,11 @@ protected virtual IEnumerable GenerateXmlElements(IEnumerable x.Content is not ILocale localeContent || !ExcludeContentLanguageFromSitemap(localeContent.Language)); @@ -389,7 +400,7 @@ protected virtual XElement GenerateSiteElement(IContent contentData, string url) if (IsDebugMode) { var language = contentData is ILocale localeContent ? localeContent.Language : CultureInfo.InvariantCulture; - var contentName = Regex.Replace(contentData.Name, "[-]+", "", RegexOptions.None); + var contentName = _dashRegex.Replace(contentData.Name, string.Empty); element.AddFirst( new XComment($"page ID: '{contentData.ContentLink.ID}', name: '{contentName}', language: '{language.Name}'")); @@ -446,22 +457,27 @@ protected virtual void AddFilteredContentElement( url = GetAbsoluteUrl(url); - var fullContentUrl = new Uri(url); + var contentUrl = new Uri(url); - if (UrlSet.Contains(fullContentUrl.ToString()) || UrlFilter.IsUrlFiltered(fullContentUrl.AbsolutePath, SitemapData)) + foreach (var fullContentUrl in _uriAugmenterService.GetAugmentUris(content, languageContentInfo, contentUrl)) { - return; - } + var fullUrl = fullContentUrl.ToString(); + + if (UrlSet.Contains(fullUrl) || UrlFilter.IsUrlFiltered(fullContentUrl.AbsolutePath, SitemapData)) + { + continue; + } - var contentElement = GenerateSiteElement(content, fullContentUrl.ToString()); + var contentElement = GenerateSiteElement(content, fullUrl); - if (contentElement == null) - { - return; - } + if (contentElement == null) + { + continue; + } - xmlElements.Add(contentElement); - UrlSet.Add(fullContentUrl.ToString()); + xmlElements.Add(contentElement); + UrlSet.Add(fullUrl); + } } protected virtual XElement CreateHrefLangElement(HrefLangData data) diff --git a/src/Geta.Optimizely.Sitemaps/XML/StandardSitemapXmlGenerator.cs b/src/Geta.Optimizely.Sitemaps/XML/StandardSitemapXmlGenerator.cs index 0d04f415..65a2a4de 100644 --- a/src/Geta.Optimizely.Sitemaps/XML/StandardSitemapXmlGenerator.cs +++ b/src/Geta.Optimizely.Sitemaps/XML/StandardSitemapXmlGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Geta Digital. All rights reserved. +// Copyright (c) Geta Digital. All rights reserved. // Licensed under Apache-2.0. See the LICENSE file in the project root for more information using EPiServer; @@ -7,6 +7,7 @@ using EPiServer.Web; using EPiServer.Web.Routing; using Geta.Optimizely.Sitemaps.Repositories; +using Geta.Optimizely.Sitemaps.Services; using Geta.Optimizely.Sitemaps.Utils; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; @@ -22,6 +23,7 @@ public StandardSitemapXmlGenerator( ISiteDefinitionRepository siteDefinitionRepository, ILanguageBranchRepository languageBranchRepository, IContentFilter contentFilter, + IUriAugmenterService uriAugmenterService, ISynchronizedObjectInstanceCache objectCache, IMemoryCache cache, ILogger logger) @@ -32,6 +34,7 @@ public StandardSitemapXmlGenerator( siteDefinitionRepository, languageBranchRepository, contentFilter, + uriAugmenterService, objectCache, cache, logger)