├── src ├── TcbInternetSolutions.Vulcan.Core │ ├── releasenotes.txt │ ├── readme.md │ ├── IVulcanContentHit.cs │ ├── VulcanIgnoreAttribute.cs │ ├── IVulcanIndexer.cs │ ├── VulcanSearchableAttribute.cs │ ├── IVulcanSearchHitDescription.cs │ ├── Internal │ │ ├── IVulcanFeature.cs │ │ ├── IVulcanFeatureCacheScope.cs │ │ ├── IVulcanContentIndexerWithCacheClearing.cs │ │ ├── VulcanFeatureCacheScope.cs │ │ └── AppConfigurationHelper.cs │ ├── IVulcanSearchable.cs │ ├── IVulcanIndexingModifier.cs │ ├── IVulcanMediaReader.cs │ ├── IVulcanContentIndexer.cs │ ├── IVulcanConnectionSettings.cs │ ├── IVulcanConnectionPoolFactory.cs │ ├── IVulcanModfiySerializerSettings.cs │ ├── IVulcanConditionalContentIndexInstruction.cs │ ├── IVulcanContentAncestorLoader.cs │ ├── Extensions │ │ ├── CultureExtensions.cs │ │ ├── StringExtensions.cs │ │ ├── QueryContainerExtensions.cs │ │ ├── TypeExtensions.cs │ │ ├── FieldExtensions.cs │ │ ├── UserExtensions.cs │ │ └── ContentAreaExtensions.cs │ ├── Resources │ │ └── LanguageFiles │ │ │ └── Vulcan_en.xml │ ├── Implementation │ │ ├── DefaultVulcanBytesToStringConverter.cs │ │ ├── VulcanSearchHit.cs │ │ ├── VulcanSynonym.cs │ │ ├── DefaultVulcanIndexContentJobSettings.cs │ │ ├── DefaultVulcanModifySerializerSettings.cs │ │ ├── VulcanCreateIndexCustomizer.cs │ │ ├── VulcanConditionalContentIndexInstruction.cs │ │ ├── VulcanIndexingModifierArgs.cs │ │ ├── VulcanCmsContentAncestorLoader.cs │ │ ├── VulcanConnectionPoolFactory.cs │ │ ├── VulcanIndexClearJob.cs │ │ ├── VulcanSearchContentLoader.cs │ │ ├── VulcanContentHit.cs │ │ ├── VulcanApplicationSettings.cs │ │ ├── VulcanPipelineSelector.cs │ │ ├── VulcanSearchHitList.cs │ │ ├── VulcanCmsIndexer.cs │ │ ├── VulcanMediaReader.cs │ │ ├── Converters │ │ │ └── ContentReferenceConverter.cs │ │ ├── VulcanCmsIndexingModifier.cs │ │ └── VulcanClientConnectionSettings.cs │ ├── IVulcanByteToStringConverter.cs │ ├── IVulcanPipelineInstaller.cs │ ├── IVulcanIndexingModifierArgs.cs │ ├── IVulcanPipeline.cs │ ├── IVulcanPipelineSelector.cs │ ├── IVulcanSearchContentLoader.cs │ ├── IVulcanIndexContentJobSettings.cs │ ├── Initialization │ │ └── RegisterImplementations.cs │ ├── IVulcanCreateIndexCustomizer.cs │ ├── IVulcanApplicationSettings.cs │ ├── IVulcanCustomizer.cs │ ├── IVulcanPocoIndexer.cs │ ├── IVulcanPocoIndexingJob.cs │ ├── VulcanPocoIndexingJobBase.cs │ ├── VulcanFieldConstants.cs │ ├── TcbInternetSolutions.Vulcan.Core.csproj │ ├── IVulcanClient.cs │ └── IVulcanHandler.cs ├── TcbInternetSolutions.Vulcan.UI │ ├── releasenotes.txt │ ├── readme.md │ ├── TcbInternetSolutions.Vulcan.UI │ │ ├── module.config │ │ ├── Views │ │ │ ├── Shared │ │ │ │ └── Layouts │ │ │ │ │ └── _epi.cshtml │ │ │ └── web.config │ │ └── Scripts │ │ │ └── DijitRegistry.js │ ├── Controllers │ │ ├── Base │ │ │ └── BaseController.cs │ │ ├── VulcanApiController.cs │ │ └── HomeController.cs │ ├── web.config.install.xdt │ ├── web.config.uninstall.xdt │ ├── Models │ │ └── ViewModels │ │ │ └── HomeViewModel.cs │ ├── Support │ │ ├── RegisterRoutes.cs │ │ └── Helper.cs │ └── TcbInternetSolutions.Vulcan.UI.csproj ├── TcbInternetSolutions.Vulcan.Commerce │ ├── releasenotes.txt │ ├── Initialization │ │ └── RegisterImplementations.cs │ ├── VulcanCommerceIndexer.cs │ ├── VulcanFieldHelper.cs │ ├── VulcanCommerceContentSynchronization.cs │ ├── Extensions │ │ └── PriceExtensions.cs │ ├── VulcanCatalogSearchProvider.cs │ ├── TcbInternetSolutions.Vulcan.Commerce.csproj │ ├── VulcanPriceReindexTrigger.cs │ └── VulcanCommerceContentAncestorLoader.cs ├── TcbInternetSolutions.Vulcan.AttachmentIndexer │ ├── releasenotes.txt │ ├── IVulcanAttachmentInspector.cs │ ├── web.config.install.xdt │ ├── IVulcanAttachmentIndexerSettings.cs │ ├── Implementation │ │ ├── VulcanAttachmentPipeline.cs │ │ ├── VulcanAttachmentInspector.cs │ │ ├── VulcanAttachmentIndexerSettings.cs │ │ └── VulcanAttachmentPipelineInstaller.cs │ ├── packages.config │ ├── TcbInternetSolutions.Vulcan.AttachmentIndexer.csproj │ ├── readme.md │ └── VulcanAttachmentIndexModifier.cs ├── TcbInternetSolutions.Vulcan.Core.SearchProviders │ ├── releasenotes.txt │ ├── Extensions │ │ └── SearchProviderExtensions.cs │ ├── TcbInternetSolutions.Vulcan.Core.SearchProviders.csproj │ ├── VulcanPageSearchProvider.cs │ ├── VulcanBlockSearchProvider.cs │ └── VulcanMediaSearchProvider.cs ├── TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed │ ├── releasenotes.txt │ ├── IGoogleProductFeedService.cs │ ├── GoogleProductFeedDefault.cs │ ├── GoogleProductFeedEntry.cs │ ├── IGoogleProductFeed.cs │ ├── GoogleProductFeedController.cs │ ├── TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed.csproj │ └── GoogleProductFeedService.cs ├── TcbInternetSolutions.Vulcan.Commerce.SearchProviders │ ├── releasenotes.txt │ └── TcbInternetSolutions.Vulcan.Commerce.SearchProviders.csproj └── targets │ ├── AddReleaseNotes.targets │ ├── AddReadMeMarkdown.targets │ ├── CommonBuild.props │ └── ZipEpiserverModule.targets ├── appveyor.yml ├── .nuget └── NuGet.Config ├── License.txt ├── README.md ├── .gitignore └── TcbInternetSolutions.Vulcan.sln /src/TcbInternetSolutions.Vulcan.Core/releasenotes.txt: -------------------------------------------------------------------------------- 1 | Support for Epi Commerce 13. -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/releasenotes.txt: -------------------------------------------------------------------------------- 1 | Support for Epi Commerce 13. -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce/releasenotes.txt: -------------------------------------------------------------------------------- 1 | Support for Epi Commerce 13. -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/releasenotes.txt: -------------------------------------------------------------------------------- 1 | Support for Epi Commerce 13. -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core.SearchProviders/releasenotes.txt: -------------------------------------------------------------------------------- 1 | Support for Epi Commerce 13. -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed/releasenotes.txt: -------------------------------------------------------------------------------- 1 | Support for Epi Commerce 13. -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce.SearchProviders/releasenotes.txt: -------------------------------------------------------------------------------- 1 | Support for Epi Commerce 13. -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/readme.md: -------------------------------------------------------------------------------- 1 | # TcbInternetSolutions.Vulcan.UI Read Me 2 | 3 | Adds Vulan section in Episerver UI. -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/readme.md: -------------------------------------------------------------------------------- 1 | # TcbInternetSolutions.Vulcan.Core Read Me 2 | 3 | Supports net461+ and netstandard2.0+ Episerver projects. -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | configuration: Release 3 | platform: Any CPU 4 | before_build: 5 | - cmd: nuget restore 6 | build: 7 | verbosity: minimal 8 | after_test: 9 | - cmd: dotnet pack --output ..\..\ 10 | artifacts: 11 | - path: '*.nupkg' -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/TcbInternetSolutions.Vulcan.UI/module.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanContentHit.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core 4 | { 5 | /// 6 | /// Vulcan content hit 7 | /// 8 | public interface IVulcanContentHit : IContent { } 9 | } 10 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/VulcanIgnoreAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core 4 | { 5 | /// 6 | /// Allows property to be ignored by vulcan indexer 7 | /// 8 | [AttributeUsage(AttributeTargets.Property)] 9 | public class VulcanIgnoreAttribute : Attribute { } 10 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanIndexer.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | /// 4 | /// Vulcan indexer contract 5 | /// 6 | public interface IVulcanIndexer 7 | { 8 | /// 9 | /// Indexer name 10 | /// 11 | string IndexerName { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/VulcanSearchableAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | using System; 4 | 5 | /// 6 | /// Allows properties to be indexed in custom search field 7 | /// 8 | [AttributeUsage(AttributeTargets.Property)] 9 | public class VulcanSearchableAttribute : Attribute { } 10 | } 11 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanSearchHitDescription.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | /// 4 | /// Search hit description 5 | /// 6 | public interface IVulcanSearchHitDescription 7 | { 8 | /// 9 | /// Search hit description 10 | /// 11 | string VulcanSearchDescription { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Internal/IVulcanFeature.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Internal 2 | { 3 | /// 4 | /// Feature flag to preview new functionality 5 | /// 6 | public interface IVulcanFeature 7 | { 8 | /// 9 | /// Determines if feature is enabled, mainly used around previewing new functionality 10 | /// 11 | bool Enabled { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanSearchable.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | /// 4 | /// Used to determine if content is searchable on front-end searches 5 | /// 6 | public interface IVulcanSearchable 7 | { 8 | /// 9 | /// Used to determine if content is searchable on front-end searches 10 | /// 11 | bool IsSearchable { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Internal/IVulcanFeatureCacheScope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core.Internal 4 | { 5 | /// 6 | /// Feature flag for testing cache scoping 7 | /// 8 | public interface IVulcanFeatureCacheScope : IVulcanFeature 9 | { 10 | /// 11 | /// Scope cache duration 12 | /// 13 | TimeSpan CacheDuration { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/Controllers/Base/BaseController.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | using TcbInternetSolutions.Vulcan.Core; 3 | 4 | namespace TcbInternetSolutions.Vulcan.UI.Controllers.Base 5 | { 6 | public abstract class BaseController : Controller 7 | { 8 | protected BaseController(IVulcanHandler vulcanHandler) 9 | { 10 | VulcanHandler = vulcanHandler; 11 | } 12 | 13 | public IVulcanHandler VulcanHandler { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanIndexingModifier.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | /// 4 | /// Index modifier 5 | /// 6 | public interface IVulcanIndexingModifier 7 | { 8 | /// 9 | /// Process modifier and flush customization to stream 10 | /// 11 | /// 12 | void ProcessContent(IVulcanIndexingModifierArgs modifierArgs); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanMediaReader.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core 4 | { 5 | /// 6 | /// Converts mediadata to byte array 7 | /// 8 | public interface IVulcanMediaReader 9 | { 10 | /// 11 | /// Converts given media data to byte array 12 | /// 13 | /// 14 | /// 15 | byte[] ReadToEnd(MediaData media); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanContentIndexer.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | using EPiServer.Core; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// Indexer for Episerver content 8 | /// 9 | public interface IVulcanContentIndexer : IVulcanIndexer 10 | { 11 | /// 12 | /// Root reference to index descendents 13 | /// 14 | /// 15 | KeyValuePair GetRoot(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanConnectionSettings.cs: -------------------------------------------------------------------------------- 1 | using Nest; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core 4 | { 5 | /// 6 | /// Vulcan connection settings 7 | /// 8 | public interface IVulcanClientConnectionSettings 9 | { 10 | /// 11 | /// Index name 12 | /// 13 | string Index { get; } 14 | 15 | /// 16 | /// Connection settings 17 | /// 18 | ConnectionSettings ConnectionSettings { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanConnectionPoolFactory.cs: -------------------------------------------------------------------------------- 1 | using Elasticsearch.Net; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core 4 | { 5 | /// 6 | /// Creates Elasticsearch connection pools 7 | /// 8 | public interface IVulcanConnectionPoolFactory 9 | { 10 | /// 11 | /// Create connection pool given url 12 | /// 13 | /// 14 | /// 15 | IConnectionPool CreateConnectionPool(string vulcanUrl); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanModfiySerializerSettings.cs: -------------------------------------------------------------------------------- 1 | using Nest; 2 | using Newtonsoft.Json; 3 | 4 | namespace TcbInternetSolutions.Vulcan.Core 5 | { 6 | /// 7 | /// Provides access to modify serialization settings 8 | /// 9 | public interface IVulcanModfiySerializerSettings 10 | { 11 | /// 12 | /// Modifier for serializer settings 13 | /// 14 | void Modifier(JsonSerializerSettings jsonSerializerSettings, IConnectionSettingsValues connectionSettingsValues); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/IVulcanAttachmentInspector.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | 3 | namespace TcbInternetSolutions.Vulcan.AttachmentIndexer 4 | { 5 | /// 6 | /// Determines if attachment can be indexed 7 | /// 8 | public interface IVulcanAttachmentInspector 9 | { 10 | /// 11 | /// Determines if given mediadata is indexable 12 | /// 13 | /// 14 | /// 15 | bool AllowIndexing(MediaData media); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanConditionalContentIndexInstruction.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core 4 | { 5 | /// 6 | /// Determines if content can be indexed 7 | /// 8 | public interface IVulcanConditionalContentIndexInstruction 9 | { 10 | /// 11 | /// Determines if content can be indexed 12 | /// 13 | /// 14 | /// 15 | bool AllowContentIndexing(IContent objectToIndex); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanContentAncestorLoader.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using System.Collections.Generic; 3 | 4 | namespace TcbInternetSolutions.Vulcan.Core 5 | { 6 | /// 7 | /// Indexing modifier that supports ancestors 8 | /// 9 | public interface IVulcanContentAncestorLoader 10 | { 11 | /// 12 | /// Gets ancestors for given content 13 | /// 14 | /// 15 | /// 16 | IEnumerable GetAncestors(IContent content); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/web.config.install.xdt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Extensions/CultureExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core.Extensions 4 | { 5 | /// 6 | /// Culture extensions 7 | /// 8 | public static class CultureExtensions 9 | { 10 | /// 11 | /// Gets name from cultureinfo 12 | /// 13 | /// 14 | /// 15 | public static string GetCultureName(this CultureInfo culture) => culture.Equals(CultureInfo.InvariantCulture) ? "invariant" : culture.Name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Resources/LanguageFiles/Vulcan_en.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vulcan Pages 8 | 9 | 10 | Vulcan Blocks 11 | 12 | 13 | Vulcan Files 14 | 15 | 16 | Vulcan Products 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Extensions 2 | { 3 | /// 4 | /// Vulcan string extesions 5 | /// 6 | public static class StringExtensions 7 | { 8 | /// 9 | /// Escapes given string as valid JSON, important, this will add escaped quotes 10 | /// 11 | /// 12 | /// 13 | public static string JsonEscapeString(this string s) 14 | { 15 | return Newtonsoft.Json.JsonConvert.ToString(s); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/targets/AddReleaseNotes.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | releasenotes.txt 5 | $([System.IO.File]::ReadAllText($(ReleaseNotesPath))) 6 | $(ReleaseNotes) 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/DefaultVulcanBytesToStringConverter.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 2 | { 3 | using EPiServer.ServiceLocation; 4 | 5 | /// 6 | /// Default implementation does nothing 7 | /// 8 | [ServiceConfiguration(typeof(IVulcanBytesToStringConverter), Lifecycle = ServiceInstanceScope.Singleton)] 9 | public class DefaultVulcanBytesToStringConverter : IVulcanBytesToStringConverter 10 | { 11 | string IVulcanBytesToStringConverter.ConvertToString(byte[] bytes, string mimeType) 12 | { 13 | return string.Empty; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Internal/IVulcanContentIndexerWithCacheClearing.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Internal 2 | { 3 | /// 4 | /// Indexer that can clear cache, temporary until full migration of using cache scoping 5 | /// 6 | public interface IVulcanContentIndexerWithCacheClearing : IVulcanContentIndexer 7 | { 8 | /// 9 | /// Clear cache every X items 10 | /// 11 | int ClearCacheItemInterval { get; } 12 | 13 | /// 14 | /// Clears any cache - so keeping performance high 15 | /// 16 | void ClearCache(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/targets/AddReadMeMarkdown.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanByteToStringConverter.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | /// 4 | /// Converts byte array to string, default implementation does nothing, to override use an IConfigurableModule with a ModuleDependency on ServiceConfiguration 5 | /// 6 | public interface IVulcanBytesToStringConverter 7 | { 8 | /// 9 | /// Converts bytes to string 10 | /// 11 | /// 12 | /// 13 | /// 14 | string ConvertToString(byte[] bytes, string mimeType); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/web.config.install.xdt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/web.config.uninstall.xdt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanPipelineInstaller.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | /// 4 | /// Installs a pipeline on Epi initialization 5 | /// 6 | public interface IVulcanPipelineInstaller 7 | { 8 | /// 9 | /// Name/Id of pipeline, must match an IVulcanPipeline 10 | /// 11 | string Id { get; } 12 | 13 | /// 14 | /// Exectutes put pipeline and any other tasks needed, may throw exceptions if setup doesn't meet requirements 15 | /// 16 | /// 17 | /// 18 | void Install(IVulcanClient vulcanClient); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed/IGoogleProductFeedService.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Commerce.Catalog.ContentTypes; 2 | using System; 3 | 4 | namespace TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed 5 | { 6 | public interface IGoogleProductFeedService 7 | { 8 | IGoogleProductFeed CreateFeed(string urlSegment) where T : VariationContent; 9 | 10 | IGoogleProductFeed CreateFeed(string urlSegment) where TGoogleProductFeed : IGoogleProductFeed, new() where TVariationContent : VariationContent; 11 | 12 | IGoogleProductFeed GetFeed() where T : VariationContent; 13 | 14 | IGoogleProductFeed GetFeed(Type type); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed/GoogleProductFeedDefault.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Commerce.Catalog.ContentTypes; 2 | using EPiServer.Framework; 3 | using EPiServer.Framework.Initialization; 4 | 5 | namespace TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed 6 | { 7 | [InitializableModule] 8 | [ModuleDependency(typeof(EPiServer.Web.InitializationModule))] 9 | public class GoogleProductFeedDefault : IInitializableModule 10 | { 11 | public void Initialize(InitializationEngine context) 12 | { 13 | context.Locate.Advanced.GetInstance().CreateFeed("Default"); 14 | } 15 | 16 | public void Uninitialize(InitializationEngine context) 17 | { 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanIndexingModifierArgs.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using System.Collections.Generic; 3 | 4 | namespace TcbInternetSolutions.Vulcan.Core 5 | { 6 | /// 7 | /// Arguments used to modify indexing 8 | /// 9 | public interface IVulcanIndexingModifierArgs 10 | { 11 | /// 12 | /// Content Instance 13 | /// 14 | IContent Content { get; } 15 | 16 | /// 17 | /// Matched pipeline Id or null 18 | /// 19 | string PipelineId { get; } 20 | 21 | /// 22 | /// Additional items to serialize for item 23 | /// 24 | IDictionary AdditionalItems { get; } 25 | } 26 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanSearchHit.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 2 | { 3 | /// 4 | /// Vulcan search hit 5 | /// 6 | public class VulcanSearchHit 7 | { 8 | /// 9 | /// Id 10 | /// 11 | public virtual object Id { get; set; } 12 | 13 | /// 14 | /// Summary 15 | /// 16 | public virtual string Summary { get; set; } 17 | 18 | /// 19 | /// Title 20 | /// 21 | public virtual string Title { get; set; } 22 | 23 | /// 24 | /// Url 25 | /// 26 | public virtual string Url { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanSynonym.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 2 | { 3 | /// 4 | /// Vulcan synonym 5 | /// 6 | public class VulcanSynonym 7 | { 8 | /// 9 | /// Language name 10 | /// 11 | public string Language { get; set; } 12 | 13 | /// 14 | /// Term 15 | /// 16 | public string Term { get; set; } 17 | 18 | /// 19 | /// Synonym list 20 | /// 21 | public string[] Synonyms { get; set; } 22 | 23 | /// 24 | /// Bi-directional 25 | /// 26 | public bool BiDirectional { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/DefaultVulcanIndexContentJobSettings.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 4 | { 5 | /// 6 | /// Default Index job settings 7 | /// 8 | [ServiceConfiguration(typeof(IVulcanIndexContentJobSettings), Lifecycle = ServiceInstanceScope.Singleton)] 9 | public class DefaultVulcanIndexContentJobSettings : IVulcanIndexContentJobSettings 10 | { 11 | bool IVulcanIndexContentJobSettings.EnableParallelIndexers => false; 12 | 13 | bool IVulcanIndexContentJobSettings.EnableParallelContent => false; 14 | 15 | bool IVulcanIndexContentJobSettings.EnableAlwaysUp => false; 16 | 17 | int IVulcanIndexContentJobSettings.ParallelDegree => 4; 18 | } 19 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanPipeline.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core 4 | { 5 | /// 6 | /// Elastic Search Pipeline 7 | /// 8 | public interface IVulcanPipeline 9 | { 10 | /// 11 | /// Pipeline name, no spaces or special characters please 12 | /// 13 | string Id { get; } 14 | 15 | /// 16 | /// Sort to determine pipeline, highest wins 17 | /// 18 | int SortOrder { get; } 19 | 20 | /// 21 | /// Determines if content needs a pipeline 22 | /// 23 | /// 24 | /// 25 | bool IsMatch(IContent content); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/DefaultVulcanModifySerializerSettings.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | using Nest; 3 | using Newtonsoft.Json; 4 | 5 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 6 | { 7 | /// 8 | /// Enables EscapeHtml for string escaping 9 | /// 10 | [ServiceConfiguration(typeof(IVulcanModfiySerializerSettings), Lifecycle = ServiceInstanceScope.Singleton)] 11 | public class DefaultVulcanModifySerializerSettings : IVulcanModfiySerializerSettings 12 | { 13 | 14 | void IVulcanModfiySerializerSettings.Modifier(JsonSerializerSettings jsonSerializerSettings, IConnectionSettingsValues connectionSettingsValues) 15 | { 16 | jsonSerializerSettings.StringEscapeHandling = StringEscapeHandling.EscapeHtml; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/targets/CommonBuild.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(APPVEYOR_BUILD_NUMBER) 5 | $(APPVEYOR_REPO_BRANCH) 6 | $(APPVEYOR_REPO_COMMIT) 7 | 8 | 9 | $(BUILD_BUILDNUMBER) 10 | $(BUILD_SOURCEVERSION) 11 | $(BUILD_SOURCEBRANCHNAME) 12 | 13 | $([System.DateTime]::Now.ToString(yyyy)) 14 | 15 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanPipelineSelector.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core 4 | { 5 | /// 6 | /// Responsible for examining content to find a pipeline for indexing 7 | /// 8 | public interface IVulcanPipelineSelector 9 | { 10 | /// 11 | /// Returns a pipeline or null if a pipeline doesn't match the content 12 | /// 13 | /// 14 | /// 15 | IVulcanPipeline GetPipelineForContent(IContent content); 16 | 17 | /// 18 | /// Returns a pipeline for given ID, used during custom serialization 19 | /// 20 | /// 21 | /// 22 | IVulcanPipeline GetPipelineById(string id); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanSearchContentLoader.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using System.Collections.Generic; 3 | 4 | namespace TcbInternetSolutions.Vulcan.Core 5 | { 6 | /// 7 | /// Loads content for Vulcan scheduled job 8 | /// 9 | public interface IVulcanSearchContentLoader 10 | { 11 | /// 12 | /// Gets all ContentReference of items to index. 13 | /// 14 | /// 15 | /// 16 | IEnumerable GetSearchContentReferences(IVulcanContentIndexer contentIndexer); 17 | 18 | /// 19 | /// Loads content by given content reference. 20 | /// 21 | /// 22 | /// 23 | IContent GetContent(ContentReference contentLink); 24 | } 25 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanIndexContentJobSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | /// 4 | /// Settings for the Indexing scheduled job 5 | /// 6 | public interface IVulcanIndexContentJobSettings 7 | { 8 | /// 9 | /// Parallel looping on indexers 10 | /// 11 | bool EnableParallelIndexers { get; } 12 | 13 | /// 14 | /// Parallel looping on content 15 | /// 16 | bool EnableParallelContent { get; } 17 | 18 | /// 19 | /// Always up on indexing 20 | /// 21 | bool EnableAlwaysUp { get; } 22 | 23 | /// 24 | /// How much parallel allowed? Defaults to 4, will likely be 4 threads. Set to -1 to allow to grab everything. 25 | /// 26 | int ParallelDegree { get; } 27 | } 28 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce/Initialization/RegisterImplementations.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework; 2 | using EPiServer.Framework.Initialization; 3 | using EPiServer.ServiceLocation; 4 | using TcbInternetSolutions.Vulcan.Core; 5 | 6 | namespace TcbInternetSolutions.Vulcan.Commerce.Initialization 7 | { 8 | /// 9 | /// Registers implementations to DI container 10 | /// 11 | [ModuleDependency(typeof(Core.Initialization.RegisterImplementations))] 12 | public class RegisterImplementations : IConfigurableModule 13 | { 14 | void IConfigurableModule.ConfigureContainer(ServiceConfigurationContext context) 15 | { 16 | context.Services.AddSingleton(); 17 | } 18 | 19 | void IInitializableModule.Initialize(InitializationEngine context) { } 20 | 21 | void IInitializableModule.Uninitialize(InitializationEngine context) { } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Initialization/RegisterImplementations.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework; 2 | using EPiServer.Framework.Initialization; 3 | using EPiServer.ServiceLocation; 4 | 5 | namespace TcbInternetSolutions.Vulcan.Core.Initialization 6 | { 7 | /// 8 | /// Registers implementations to DI container 9 | /// 10 | [ModuleDependency(typeof(ServiceContainerInitialization))] 11 | public class RegisterImplementations : IConfigurableModule 12 | { 13 | void IConfigurableModule.ConfigureContainer(ServiceConfigurationContext context) 14 | { 15 | // hack: using manual registration as scheduled job doesn't inject otherwise 16 | context.Services.AddSingleton(); 17 | } 18 | 19 | void IInitializableModule.Initialize(InitializationEngine context) { } 20 | 21 | void IInitializableModule.Uninitialize(InitializationEngine context) { } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanCreateIndexCustomizer.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | using Nest; 4 | using System; 5 | 6 | /// 7 | /// Used to customize 8 | /// 9 | public interface IVulcanCreateIndexCustomizer 10 | { 11 | /// 12 | /// Null by default, but can be used to setup shards, replicas, anaylzers, etc for the index. 13 | /// 14 | Func CustomizeIndex { get; } 15 | 16 | /// 17 | /// Trims not analyzed fields to avoid errors, should never be above 10922, default is 256 18 | /// See https://www.elastic.co/guide/en/elasticsearch/reference/current/ignore-above.html 19 | /// 20 | int IgnoreAbove { get; } 21 | 22 | /// 23 | /// Helps avoid all shards failed on first request. 24 | /// 25 | int WaitForActiveShards { get; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanCreateIndexCustomizer.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 2 | { 3 | using EPiServer.ServiceLocation; 4 | using Nest; 5 | using System; 6 | 7 | /// 8 | /// Allows for customization of index creation 9 | /// 10 | [ServiceConfiguration(typeof(IVulcanCreateIndexCustomizer), Lifecycle = ServiceInstanceScope.Singleton)] 11 | public class VulcanCreateIndexCustomizer : IVulcanCreateIndexCustomizer 12 | { 13 | /// 14 | /// Customization of create index 15 | /// 16 | public virtual Func CustomizeIndex => null; 17 | 18 | /// 19 | /// Default ignore above for stored, not analyzed strings 20 | /// 21 | public virtual int IgnoreAbove => 256; 22 | 23 | /// 24 | /// Wait for active shards 25 | /// 26 | public virtual int WaitForActiveShards => 1; 27 | } 28 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce/VulcanCommerceIndexer.cs: -------------------------------------------------------------------------------- 1 | using Mediachase.Commerce.Catalog; 2 | using Mediachase.Commerce.Engine.Caching; 3 | using System.Collections.Generic; 4 | using TcbInternetSolutions.Vulcan.Core.Internal; 5 | 6 | namespace TcbInternetSolutions.Vulcan.Commerce 7 | { 8 | public class VulcanCommerceIndexer : IVulcanContentIndexerWithCacheClearing 9 | { 10 | private readonly ReferenceConverter _referenceConverter; 11 | 12 | public VulcanCommerceIndexer(ReferenceConverter referenceConverter) 13 | { 14 | _referenceConverter = referenceConverter; 15 | } 16 | 17 | public int ClearCacheItemInterval => 100; 18 | 19 | public string IndexerName => "Commerce Content"; 20 | 21 | public void ClearCache() 22 | { 23 | CacheHelper.Clear(string.Empty); 24 | } 25 | 26 | public KeyValuePair GetRoot() => 27 | new KeyValuePair(_referenceConverter.GetRootLink(), "Commerce"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/IVulcanAttachmentIndexerSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TcbInternetSolutions.Vulcan.AttachmentIndexer 4 | { 5 | /// 6 | /// Settings for vulcan attachment indexing 7 | /// 8 | public interface IVulcanAttachmentIndexerSettings 9 | { 10 | /// 11 | /// If true, the Elasticsearch server must have mapper-attachments (2.x) or ingest-attachments (5.x) installed 12 | /// 13 | bool EnableAttachmentPlugins { get; } 14 | 15 | /// 16 | /// Determines supported file extensions for indexing. 17 | /// 18 | IEnumerable SupportedFileExtensions { get; } 19 | 20 | /// 21 | /// Determines if file size limits are used to determine indexing 22 | /// 23 | bool EnableFileSizeLimit { get; } 24 | 25 | /// 26 | /// Determines max file size of media to index 27 | /// 28 | long FileSizeLimit { get; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed/GoogleProductFeedEntry.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed 2 | { 3 | public class GoogleProductFeedEntry 4 | { 5 | public string Id { get; set; } 6 | 7 | public string Title { get; set; } 8 | 9 | public string Description { get; set; } 10 | 11 | public string Link { get; set; } 12 | 13 | public string ImageLink { get; set; } 14 | 15 | public string Availability { get; set; } 16 | 17 | public string Price { get; set; } 18 | 19 | public string GoogleProductCategory { get; set; } 20 | 21 | public string Brand { get; set; } 22 | 23 | public string GTIN { get; set; } 24 | 25 | public string MPN { get; set; } 26 | 27 | public bool IdentifierExists => !string.IsNullOrWhiteSpace(GTIN) || !string.IsNullOrWhiteSpace(MPN); 28 | 29 | public string Condition { get; set; } 30 | 31 | public string Adult { get; set; } 32 | 33 | public string Shipping { get; set; } 34 | 35 | public string Tax { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2006 Daniel Matthews 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanApplicationSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | /// 4 | /// Settings for Vulcan connections to Elastic Search 5 | /// 6 | public interface IVulcanApplicationSettings 7 | { 8 | /// 9 | /// Enables Http Compression 10 | /// 11 | bool EnableHttpCompression { get; } 12 | 13 | /// 14 | /// Vulcan Index prefix name 15 | /// 16 | string IndexNamePrefix { get; } 17 | 18 | /// 19 | /// Is debugging enabled 20 | /// 21 | bool IsDebugMode { get; } 22 | 23 | /// 24 | /// Vulcan password form connecting to Elasticsearch 25 | /// 26 | string Password { get; } 27 | 28 | /// 29 | /// URL to Elasticsearch instance 30 | /// 31 | string Url { get; } 32 | 33 | /// 34 | /// Username to connect to Elasticsearch 35 | /// 36 | string Username { get; } 37 | } 38 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/Models/ViewModels/HomeViewModel.cs: -------------------------------------------------------------------------------- 1 | using Nest; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using TcbInternetSolutions.Vulcan.Core; 6 | 7 | namespace TcbInternetSolutions.Vulcan.UI.Models.ViewModels 8 | { 9 | public class HomeViewModel 10 | { 11 | public Dictionary>> ClientViewInfo { get; set; } 12 | 13 | public List IndexHealthDescriptor { get; set; } = new List(); 14 | 15 | public IEnumerable PocoIndexers { get; set; } 16 | 17 | public IEnumerable VulcanClients { get; set; } 18 | 19 | public IVulcanHandler VulcanHandler { get; set; } 20 | 21 | public IEnumerable VulcanIndexModifiers { get; set; } 22 | 23 | public bool HasClients => VulcanClients?.Any() == true; 24 | 25 | public bool HasIndexModifiers => VulcanIndexModifiers?.Any() == true; 26 | 27 | public bool HasPocoIndexers => PocoIndexers?.Any() == true; 28 | 29 | public string ProtectedUiPath { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Internal/VulcanFeatureCacheScope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EPiServer.ServiceLocation; 3 | 4 | namespace TcbInternetSolutions.Vulcan.Core.Internal 5 | { 6 | /// 7 | /// Default Cachescope preview functionality 8 | /// 9 | [ServiceConfiguration(typeof(IVulcanFeature), Lifecycle = ServiceInstanceScope.Singleton)] 10 | public class VulcanFeatureCacheScope : IVulcanFeatureCacheScope 11 | { 12 | /// 13 | /// Constructor 14 | /// 15 | public VulcanFeatureCacheScope() 16 | { 17 | Enabled = AppConfigurationHelper.TryGetBoolFromKey 18 | ( 19 | key: nameof(VulcanFeatureCacheScope), 20 | defaultValue: false 21 | ); 22 | } 23 | 24 | /// 25 | /// Default is disabled 26 | /// 27 | public virtual bool Enabled { get; } 28 | 29 | /// 30 | /// Default cache duration is 10 seconds 31 | /// 32 | public virtual TimeSpan CacheDuration { get; } = TimeSpan.FromSeconds(value: 10); 33 | } 34 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanCustomizer.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | using Nest; 4 | using System; 5 | 6 | /// 7 | /// Used to customize mappings and analyzers for Vulcan clients, any class that inherits must have an empty public constructor! 8 | /// 9 | public interface IVulcanCustomizer 10 | { 11 | /// 12 | /// Used to add custom mappings 13 | /// 14 | Func CustomMapper { get; } 15 | 16 | /// 17 | /// Used to create custom analyzers such as EdgeNGram for autocomplete. 18 | /// 19 | Func CustomIndexUpdater { get; } 20 | 21 | /// 22 | /// Used to create custom index templates for indexes. To override field mappings effectively set the order > 0. 23 | /// Also please note back-end UI searches required an 'analyzed' multi-field, so be mindful of custom property mapping. 24 | /// 25 | Func CustomIndexTemplate { get; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanConditionalContentIndexInstruction.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using System; 3 | 4 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 5 | { 6 | /// 7 | /// Determines if content can be indexed 8 | /// 9 | /// 10 | public class VulcanConditionalContentIndexInstruction : IVulcanConditionalContentIndexInstruction where T : IContent 11 | { 12 | /// 13 | /// Func to determine indexing 14 | /// 15 | public Func Condition { get; } 16 | 17 | /// 18 | /// Constructor 19 | /// 20 | /// 21 | public VulcanConditionalContentIndexInstruction(Func condition) 22 | { 23 | Condition = condition; 24 | } 25 | 26 | /// 27 | /// Determines if content can be indexed 28 | /// 29 | /// 30 | /// 31 | public bool AllowContentIndexing(IContent objectToIndex) 32 | { 33 | return Condition?.Invoke((T)objectToIndex) ?? true; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce/VulcanFieldHelper.cs: -------------------------------------------------------------------------------- 1 | using Mediachase.Commerce; 2 | using TcbInternetSolutions.Vulcan.Core.Implementation; 3 | 4 | namespace TcbInternetSolutions.Vulcan.Commerce 5 | { 6 | public static class VulcanFieldHelper 7 | { 8 | public static string GetPriceField(string marketId = null, string currencyCode = null) => GetPriceField("prices", marketId, currencyCode); 9 | 10 | public static string GetPriceLowField(string marketId = null, string currencyCode = null) => GetPriceField("pricesLow", marketId, currencyCode); 11 | 12 | public static string GetPriceHighField(string marketId = null, string currencyCode = null) => GetPriceField("pricesHigh", marketId, currencyCode); 13 | 14 | private static string GetPriceField(string propertyName, string marketId, string currencyCode, ICurrentMarket currentMarket = null) 15 | { 16 | currentMarket = currentMarket ?? VulcanHelper.GetService(); 17 | if (marketId == null) marketId = currentMarket.GetCurrentMarket().MarketId.Value; 18 | if (currencyCode == null) currencyCode = currentMarket.GetCurrentMarket().DefaultCurrency.CurrencyCode; 19 | 20 | return "__" + propertyName + "." + marketId + "_" + currencyCode; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanIndexingModifierArgs.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 2 | { 3 | using System.Collections.Generic; 4 | using EPiServer.Core; 5 | 6 | /// 7 | /// Arguments used to modify indexing 8 | /// 9 | public class VulcanIndexingModifierArgs : IVulcanIndexingModifierArgs 10 | { 11 | /// 12 | /// Constructor 13 | /// 14 | /// 15 | /// 16 | public VulcanIndexingModifierArgs(IContent content, string pipelineId) 17 | { 18 | Content = content; 19 | PipelineId = pipelineId; 20 | AdditionalItems = new Dictionary(); 21 | } 22 | 23 | /// 24 | /// Content Instance 25 | /// 26 | public IContent Content { get; } 27 | 28 | /// 29 | /// Matched pipeline Id or null 30 | /// 31 | public string PipelineId { get; } 32 | 33 | /// 34 | /// Additional serialization items 35 | /// 36 | public IDictionary AdditionalItems { get; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanPocoIndexer.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | using System.Collections.Generic; 4 | 5 | /// 6 | /// Vulcan POCO indexer 7 | /// 8 | public interface IVulcanPocoIndexer : IVulcanIndexer 9 | { 10 | /// 11 | /// Total items to index 12 | /// 13 | long TotalItems { get; } 14 | 15 | /// 16 | /// Determines number of items to index at a time. 17 | /// 18 | int PageSize { get; } 19 | 20 | /// 21 | /// Gets a list of items given page and pagesize. 22 | /// 23 | /// 24 | /// 25 | /// 26 | IEnumerable GetItems(int page, int pageSize); 27 | 28 | /// 29 | /// Gets the ID of an item 30 | /// 31 | /// 32 | /// 33 | string GetItemIdentifier(object o); 34 | 35 | /// 36 | /// Allows dev to choose if default index job will index the poco indexer data. 37 | /// 38 | bool IncludeInDefaultIndexJob { get; } 39 | } 40 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanCmsContentAncestorLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using EPiServer; 4 | using EPiServer.Core; 5 | using EPiServer.ServiceLocation; 6 | 7 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 8 | { 9 | /// 10 | /// Gets ancestors for CMS content 11 | /// 12 | [ServiceConfiguration(typeof(IVulcanContentAncestorLoader), Lifecycle = ServiceInstanceScope.Singleton)] 13 | public class VulcanCmsContentAncestorLoader : IVulcanContentAncestorLoader 14 | { 15 | private readonly IContentLoader _contentLoader; 16 | 17 | /// 18 | /// DI Constructor 19 | /// 20 | /// 21 | public VulcanCmsContentAncestorLoader(IContentLoader contentLoader) 22 | { 23 | _contentLoader = contentLoader; 24 | } 25 | 26 | /// 27 | /// Gets IContent ancestors 28 | /// 29 | /// 30 | /// 31 | public IEnumerable GetAncestors(IContent content) 32 | { 33 | return _contentLoader.GetAncestors(content.ContentLink)?.Select(c => c.ContentLink); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/Support/RegisterRoutes.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework; 2 | using EPiServer.Framework.Initialization; 3 | using System.Web.Mvc; 4 | using System.Web.Routing; 5 | 6 | namespace TcbInternetSolutions.Vulcan.UI.Support 7 | { 8 | [InitializableModule] 9 | [ModuleDependency(typeof(EPiServer.Web.InitializationModule))] 10 | public class RegisterRoutes : IInitializableModule 11 | { 12 | public void Initialize(InitializationEngine context) 13 | { 14 | RouteTable.Routes.MapRoute( 15 | "ListSynonyms", 16 | "Vulcan-Api/Synonym/List/{Language}", 17 | new { controller = "VulcanApi", action = "ListSynonyms", Language = "" }); 18 | 19 | RouteTable.Routes.MapRoute( 20 | "AddSynonym", 21 | "Vulcan-Api/Synonym/Add/{Term}/{Synonyms}/{BiDirectional}/{Language}", 22 | new { controller = "VulcanApi", action = "AddSynonym", Language = "" }); 23 | 24 | RouteTable.Routes.MapRoute( 25 | "RemoveSynonym", 26 | "Vulcan-Api/Synonym/Remove/{Term}/{Language}", 27 | new { controller = "VulcanApi", action = "RemoveSynonym", Language = "" }); 28 | } 29 | 30 | public void Uninitialize(InitializationEngine context) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/targets/ZipEpiserverModule.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(EpiModuleFolderName) 5 | bin\$(EpiModuleFolderName).zip 6 | 7 | powershell.exe -nologo -noprofile -ExecutionPolicy Bypass -command "& { if (Test-Path $(ZipFile)) { Remove-Item $(ZipFile) } }" 8 | powershell.exe -nologo -noprofile -ExecutionPolicy Bypass -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::CreateFromDirectory('$(EpiModuleFolderName)', '$(ZipFile)'); }" 9 | 10 | .\Content\modules\_protected\$(EpiModuleFolderName) 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce/VulcanCommerceContentSynchronization.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Commerce.Catalog.Provider; 2 | using EPiServer.Framework; 3 | using EPiServer.Framework.Initialization; 4 | using EPiServer.ServiceLocation; 5 | using TcbInternetSolutions.Vulcan.Core; 6 | 7 | namespace TcbInternetSolutions.Vulcan.Commerce 8 | { 9 | [InitializableModule] 10 | [ModuleDependency(typeof(EPiServer.Web.InitializationModule))] 11 | 12 | public class VulcanCommerceContentSynchronization : IInitializableModule 13 | { 14 | public Injected CatalogEventHandler { get; set; } 15 | 16 | public Injected VulcanHandler { get; set; } 17 | 18 | public void Initialize(InitializationEngine context) 19 | { 20 | CatalogEventHandler.Service.RelationsUpdated += VulcanCommerceContentSynchronization_RelationsUpdated; 21 | } 22 | 23 | private void VulcanCommerceContentSynchronization_RelationsUpdated(object sender, EPiServer.ContentEventArgs e) 24 | { 25 | VulcanHandler.Service.IndexContentEveryLanguage(e.ContentLink); 26 | } 27 | 28 | public void Uninitialize(InitializationEngine context) 29 | { 30 | CatalogEventHandler.Service.RelationsUpdated -= VulcanCommerceContentSynchronization_RelationsUpdated; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed/IGoogleProductFeed.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Commerce.Catalog.ContentTypes; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Web.Routing; 5 | 6 | namespace TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed 7 | { 8 | public interface IGoogleProductFeed 9 | { 10 | string UrlSegment { get; set; } 11 | 12 | Route Route { get; set; } 13 | 14 | IList GetEntries(string market, string language, string currency); 15 | } 16 | 17 | public interface IGoogleProductFeed : IGoogleProductFeed where T : VariationContent 18 | { 19 | Func, Nest.SearchDescriptor> Query { get; set; } 20 | 21 | Func DescriptionSelector { get; set; } 22 | 23 | Func AvailabilitySelector { get; set; } 24 | 25 | Func GoogleProductCategorySelector { get; set; } 26 | 27 | Func BrandSelector { get; set; } 28 | 29 | Func GTINSelector { get; set; } 30 | 31 | Func MPNSelector { get; set; } 32 | 33 | Func ShippingSelector { get; set; } 34 | 35 | Func TaxSelector { get; set; } 36 | 37 | Func ConditionSelector { get; set; } 38 | 39 | Func AdultSelector { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanPocoIndexingJob.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | using System; 4 | 5 | /// 6 | /// Vulcan POCO indexing job 7 | /// 8 | public interface IVulcanPocoIndexingJob 9 | { 10 | /// 11 | /// Indexer job 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | string Index(IVulcanPocoIndexer pocoIndexer, Action updateStatus, ref int count, ref bool stopSignaled, string alias = null); 20 | 21 | /// 22 | /// Index item 23 | /// 24 | /// 25 | /// 26 | /// 27 | void IndexItem(IVulcanPocoIndexer pocoIndexer, object item, string alias = null); 28 | 29 | /// 30 | /// Delete item 31 | /// 32 | /// 33 | /// 34 | /// 35 | void DeleteItem(IVulcanPocoIndexer pocoIndexer, object item, string alias = null); 36 | } 37 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanConnectionPoolFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Elasticsearch.Net; 4 | using EPiServer.ServiceLocation; 5 | 6 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 7 | { 8 | /// 9 | /// Creates single and static node connection pools 10 | /// 11 | [ServiceConfiguration(typeof(IVulcanConnectionPoolFactory), Lifecycle = ServiceInstanceScope.Singleton)] 12 | public class VulcanConnectionPoolFactory : IVulcanConnectionPoolFactory 13 | { 14 | /// 15 | /// Creates single and static node connection pools from given url. 16 | /// 17 | public IConnectionPool CreateConnectionPool(string vulcanUrl) 18 | { 19 | if (string.IsNullOrWhiteSpace(vulcanUrl)) throw new ArgumentNullException(nameof(vulcanUrl)); 20 | 21 | IConnectionPool connectionPool; 22 | var urls = vulcanUrl.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); 23 | 24 | if (urls.Length == 1) 25 | { 26 | connectionPool = new SingleNodeConnectionPool(new Uri(vulcanUrl)); 27 | } 28 | else 29 | { 30 | var nodeUris = urls.Select(u => new Uri(u)); 31 | connectionPool = new StaticConnectionPool(nodeUris); 32 | } 33 | 34 | return connectionPool; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanIndexClearJob.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Logging; 2 | using EPiServer.PlugIn; 3 | using EPiServer.Scheduler; 4 | 5 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 6 | { 7 | /// 8 | /// Index clear job 9 | /// 10 | [ScheduledPlugIn(DisplayName = "[Vulcan] Index Clear", SortIndex = 1110, Description = "Use this job to clear ALL indexes for this site in Vulcan. This is useful for cleanup purposes but shouldn't need to be done regularly.")] 11 | public class VulcanIndexClearJob : ScheduledJobBase 12 | { 13 | private static readonly ILogger Logger = LogManager.GetLogger(); 14 | private readonly IVulcanHandler _vulcanHandler; 15 | 16 | /// 17 | /// DI Constructor 18 | /// 19 | /// 20 | public VulcanIndexClearJob(IVulcanHandler vulcanHandler) 21 | { 22 | _vulcanHandler = vulcanHandler; 23 | IsStoppable = true; 24 | } 25 | 26 | /// 27 | /// Execute index clear job 28 | /// 29 | /// 30 | public override string Execute() 31 | { 32 | OnStatusChanged($"Starting execution of {GetType()}"); 33 | Logger.Warning("Clearing all indexes..."); 34 | _vulcanHandler.DeleteIndex(); 35 | Logger.Warning("All indexes cleared."); 36 | return "Vulcan successfully cleared all indexes!"; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/TcbInternetSolutions.Vulcan.UI/Views/Shared/Layouts/_epi.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Framework.Web.Resources 2 | @using EPiServer.Shell 3 | @using EPiServer.Shell.Navigation 4 | @using EPiServer.Shell.Web.Mvc.Html 5 | @{ 6 | // ReSharper disable UnknownCssClass 7 | } 8 | 9 | 10 | 11 | @ViewBag.Title 12 | 13 | @Html.Raw(ClientResources.RenderResources("ShellCore", new[] { ClientResourceType.Style })) 14 | @Html.Raw(ClientResources.RenderResources("epi.shell.ui", new[] { ClientResourceType.Style })) 15 | @Html.Raw(ClientResources.RenderResources("DojoDashboardCompatibility", new[] { ClientResourceType.Style })) 16 | 17 | 18 | 19 | 20 | 21 | 22 | @Html.Raw(Html.GlobalMenu()) 23 |
24 | @RenderBody() 25 |
26 | @Html.Raw(ClientResources.RenderResources("DojoDashboardCompatibility", new[] { ClientResourceType.Script })) 27 | @Html.Raw(ClientResources.RenderResources("epi.shell.ui", new[] { ClientResourceType.Script })) 28 | @Html.Raw(Html.ShellInitializationScript()) 29 | 30 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanSearchContentLoader.cs: -------------------------------------------------------------------------------- 1 | using EPiServer; 2 | using EPiServer.Core; 3 | using EPiServer.ServiceLocation; 4 | using System.Collections.Generic; 5 | 6 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 7 | { 8 | /// 9 | /// Default content loader for scheduled job 10 | /// 11 | [ServiceConfiguration(typeof(IVulcanSearchContentLoader), Lifecycle = ServiceInstanceScope.Transient)] 12 | public class VulcanSearchContentLoader : IVulcanSearchContentLoader 13 | { 14 | private readonly IContentLoader _contentLoader; 15 | 16 | /// 17 | /// Constructor 18 | /// 19 | /// 20 | public VulcanSearchContentLoader(IContentLoader contentLoader) 21 | { 22 | _contentLoader = contentLoader; 23 | } 24 | 25 | /// 26 | /// Loads content 27 | /// 28 | /// 29 | /// 30 | public virtual IContent GetContent(ContentReference contentLink) 31 | { 32 | return _contentLoader.Get(contentLink); 33 | } 34 | 35 | /// 36 | /// Loads all root descendents by default 37 | /// 38 | /// 39 | /// 40 | public virtual IEnumerable GetSearchContentReferences(IVulcanContentIndexer contentIndexer) 41 | { 42 | return _contentLoader.GetDescendents(contentIndexer.GetRoot().Key); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/VulcanPocoIndexingJobBase.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core 2 | { 3 | using System; 4 | using EPiServer.Scheduler; 5 | using EPiServer.ServiceLocation; 6 | 7 | /// 8 | /// Base class to allow creating individual poco indexing jobs per indexer. 9 | /// 10 | public abstract class VulcanPocoIndexingJobBase : ScheduledJobBase 11 | { 12 | private bool _stopSignaled; 13 | 14 | /// 15 | /// Constructor 16 | /// 17 | protected VulcanPocoIndexingJobBase() 18 | { 19 | IsStoppable = true; 20 | } 21 | 22 | /// 23 | /// Poco indexer 24 | /// 25 | protected abstract IVulcanPocoIndexer PocoIndexer { get; set; } 26 | 27 | /// 28 | /// Poco indexing job 29 | /// 30 | protected Injected VulcanPocoIndexHandler { get; set; } 31 | 32 | /// 33 | /// Execute poco indexing 34 | /// 35 | /// 36 | public override string Execute() 37 | { 38 | if (PocoIndexer == null) 39 | throw new NullReferenceException($"{nameof(PocoIndexer)} cannot be null!"); 40 | 41 | var count = 0; 42 | var result = VulcanPocoIndexHandler.Service.Index(PocoIndexer, OnStatusChanged, ref count, ref _stopSignaled); 43 | 44 | return result; 45 | } 46 | 47 | /// 48 | /// Signal stop 49 | /// 50 | public override void Stop() 51 | { 52 | _stopSignaled = true; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/Implementation/VulcanAttachmentPipeline.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.ServiceLocation; 3 | using TcbInternetSolutions.Vulcan.Core; 4 | 5 | namespace TcbInternetSolutions.Vulcan.AttachmentIndexer.Implementation 6 | { 7 | /// 8 | /// Determines if IContent enables the attachment pipeline 9 | /// 10 | [ServiceConfiguration(typeof(IVulcanPipeline), Lifecycle = ServiceInstanceScope.Singleton)] 11 | public class VulcanAttachmentPipeline : IVulcanPipeline 12 | { 13 | private readonly IVulcanAttachmentInspector _vulcanAttachmentInspector ; 14 | 15 | /// 16 | /// DI Constructor 17 | /// 18 | /// 19 | public VulcanAttachmentPipeline(IVulcanAttachmentInspector vulcanAttachmentInspector) 20 | { 21 | _vulcanAttachmentInspector = vulcanAttachmentInspector; 22 | } 23 | 24 | /// 25 | /// Pipeline ID/name 26 | /// 27 | public string Id => VulcanAttachmentPipelineInstaller.PipelineId; 28 | 29 | /// 30 | /// Pipeline sort order 31 | /// 32 | public int SortOrder => 100; 33 | 34 | /// 35 | /// Determines if content matches pipeline 36 | /// 37 | /// 38 | /// 39 | public bool IsMatch(IContent content) 40 | { 41 | if (content is MediaData mediaContent) 42 | { 43 | return _vulcanAttachmentInspector.AllowIndexing(mediaContent); 44 | } 45 | 46 | return false; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Extensions/QueryContainerExtensions.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using Nest; 3 | using System; 4 | 5 | namespace TcbInternetSolutions.Vulcan.Core.Extensions 6 | { 7 | /// 8 | /// Query extensions 9 | /// 10 | public static class QueryContainerExtensions 11 | { 12 | /// 13 | /// Adds published filter for expired, deleted 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | public static QueryContainer FilterForPublished(this QueryContainer query, bool requireIsSearchable = false) where T : class, IContent 20 | { 21 | var notDeleted = new QueryContainerDescriptor().Term(t => t.Field(xf => xf.IsDeleted).Value(false)); 22 | var published = new QueryContainerDescriptor().DateRange(dr => dr.LessThanOrEquals(DateTime.Now).Field(xf => xf.StartPublish)); 23 | var notExpired = new QueryContainerDescriptor().DateRange(dr => dr.GreaterThanOrEquals(DateTime.Now).Field(xf => xf.StopPublish)); 24 | var searchable = new QueryContainerDescriptor().Term(t => t.Field(xf => xf.IsSearchable).Value(true)); 25 | var expiredExists = new QueryContainerDescriptor().Exists(dr => dr.Field(xf => xf.StopPublish)); 26 | var filtered = notDeleted && published && (notExpired && expiredExists || !expiredExists); 27 | 28 | if (requireIsSearchable) 29 | filtered = filtered && searchable; 30 | 31 | return new QueryContainerDescriptor().Bool(b => b.Must(query).Filter(filtered)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/Support/Helper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using EPiServer.Web.Hosting; 4 | using TcbInternetSolutions.Vulcan.Core.Implementation; 5 | 6 | namespace TcbInternetSolutions.Vulcan.UI.Support 7 | { 8 | public static class Helper 9 | { 10 | public static string ResolveView(string view, VirtualPathRegistrationHandler vppHandler = null) 11 | { 12 | var resolvedVppHandler = vppHandler ?? VulcanHelper.GetService(); 13 | var protectedModulesVpp = resolvedVppHandler 14 | .RegisteredVirtualPathProviders.Where(p => p.Key is VirtualPathNonUnifiedProvider && ((VirtualPathNonUnifiedProvider) p.Key).ProviderName == "ProtectedModules") 15 | .ToList(); 16 | 17 | if (!protectedModulesVpp.Any() ||!(protectedModulesVpp[0].Key is VirtualPathNonUnifiedProvider provider)) 18 | throw new Exception("Cannot resolve the Vulcan UI views"); 19 | 20 | var path = provider.ConfigurationParameters["physicalPath"].Replace(@"\", "/"); 21 | 22 | if (!path.StartsWith("~")) 23 | { 24 | if (!path.StartsWith("/")) 25 | { 26 | path = "~/" + path; 27 | } 28 | else 29 | { 30 | path = "~" + path; 31 | } 32 | } 33 | else if (!path.StartsWith("/")) 34 | { 35 | if (!path.StartsWith("~")) 36 | { 37 | path = "~/" + path; 38 | } 39 | } 40 | 41 | if (!path.EndsWith("/")) path += "/"; 42 | 43 | if (!view.StartsWith("/")) view = "/" + view; 44 | 45 | return path + "TcbInternetSolutions.Vulcan.UI/Views" + view; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce/Extensions/PriceExtensions.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using Mediachase.Commerce; 3 | using System.Collections.Generic; 4 | using TcbInternetSolutions.Vulcan.Core.Implementation; 5 | 6 | namespace TcbInternetSolutions.Vulcan.Commerce.Extensions 7 | { 8 | public static class PriceExtensions 9 | { 10 | public static decimal GetPrice(this IContent contentHit, string marketId = null, string currencyCode = null) => 11 | contentHit is VulcanContentHit ? GetPrice(((VulcanContentHit) contentHit).__prices, marketId, currencyCode) : 0; 12 | 13 | public static decimal GetPriceLow(this IContent contentHit, string marketId = null, string currencyCode = null) => 14 | contentHit is VulcanContentHit ? GetPrice(((VulcanContentHit) contentHit).__pricesLow, marketId, currencyCode) : 0; 15 | 16 | public static decimal GetPriceHigh(this IContent contentHit, string marketId = null, string currencyCode = null) => 17 | contentHit is VulcanContentHit ? GetPrice(((VulcanContentHit) contentHit).__pricesHigh, marketId, currencyCode) : 0; 18 | 19 | private static decimal GetPrice(IReadOnlyDictionary priceDictionary, string marketId, string currencyCode, ICurrentMarket currentMarket = null) 20 | { 21 | currentMarket = currentMarket ?? VulcanHelper.GetService(); 22 | if (marketId == null) marketId = currentMarket.GetCurrentMarket().MarketId.Value; 23 | if (currencyCode == null) currencyCode = currentMarket.GetCurrentMarket().DefaultCurrency.CurrencyCode; 24 | 25 | var key = marketId + "_" + currencyCode; 26 | 27 | if(priceDictionary != null && priceDictionary.ContainsKey(key)) 28 | { 29 | return priceDictionary[key]; 30 | } 31 | 32 | return 0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core.SearchProviders/Extensions/SearchProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.Editor; 3 | using EPiServer.Shell; 4 | using System.Linq; 5 | 6 | namespace TcbInternetSolutions.Vulcan.Core.SearchProviders.Extensions 7 | { 8 | /// 9 | /// Search extensions 10 | /// 11 | public static class SearchProviderExtensions 12 | { 13 | /// 14 | /// Gets the URI for this instance. 15 | /// 16 | /// The content. 17 | /// 18 | /// An that represents the type and id of the item. 19 | /// 20 | public static string GetUri(this IContent content) => GetUri(content, false); 21 | 22 | /// 23 | /// Gets the URI for this instance. 24 | /// 25 | /// The content. 26 | /// if set to true creates a version unspecific link. 27 | /// 28 | /// An that represents the type and id of the item. 29 | /// 30 | public static string GetUri(this IContent content, bool createVersionUnspecificLink) 31 | { 32 | var contentReference = createVersionUnspecificLink ? content.ContentLink.ToReferenceWithoutVersion() : content.ContentLink; 33 | 34 | return PageEditing.GetEditUrl(contentReference); 35 | } 36 | 37 | /// 38 | /// Gets the type identifier of the content. 39 | /// 40 | /// The content. 41 | /// 42 | public static string GetTypeIdentifier(this IContent content, UIDescriptorRegistry uiDescriptorRegistry) => uiDescriptorRegistry.GetTypeIdentifiers(content.GetType()).FirstOrDefault(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Extensions 2 | { 3 | using EPiServer.Core; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | /// 10 | /// Type extensions 11 | /// 12 | public static class TypeExtensions 13 | { 14 | private static readonly ConcurrentDictionary> ResolvedTypes = new ConcurrentDictionary>(); 15 | 16 | /// 17 | /// Get search types for given T 18 | /// 19 | /// 20 | /// 21 | /// 22 | public static List GetSearchTypesFor(Func filter = null) where T : class, IContent => 23 | GetSearchTypesFor(typeof(T), filter); 24 | 25 | /// 26 | /// Get search types for given type 27 | /// 28 | /// 29 | /// 30 | /// 31 | public static List GetSearchTypesFor(this Type type, Func filter = null) 32 | { 33 | if (!ResolvedTypes.TryGetValue(type, out var allTypesForGiven)) 34 | { 35 | allTypesForGiven = new List(); 36 | 37 | foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 38 | { 39 | allTypesForGiven.AddRange(assembly.GetTypes() 40 | .Where(t => type.IsAssignableFrom(t) && t.FullName?.EndsWith("Proxy") == false)); 41 | } 42 | 43 | ResolvedTypes.TryAdd(type, allTypesForGiven); 44 | } 45 | 46 | if (filter != null) 47 | allTypesForGiven = allTypesForGiven.Where(filter).ToList(); 48 | 49 | return allTypesForGiven; 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/VulcanFieldConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core 4 | { 5 | /// 6 | /// Vulcan Constants 7 | /// 8 | public class VulcanFieldConstants 9 | { 10 | /// 11 | /// Analyzed modifier 12 | /// 13 | public static readonly string AnalyzedModifier = "analyzed"; 14 | 15 | /// 16 | /// Ancestors field 17 | /// 18 | public static readonly string Ancestors = "__ancestors"; 19 | 20 | /// 21 | /// Read permission field 22 | /// 23 | public static readonly string ReadPermission = "__readPermission"; 24 | 25 | /// 26 | /// Custom contents field 27 | /// 28 | public static readonly string CustomContents = "__customContents"; 29 | 30 | /// 31 | /// Binary media contents field, needs mapper-attachments 32 | /// 33 | public static readonly string MediaContents = "__mediaContents"; 34 | 35 | /// 36 | /// Media string contents, needs a custom IVulcanByteToStringConverter 37 | /// 38 | public static readonly string MediaStringContents = "__mediaStringContents"; 39 | 40 | /// 41 | /// Type field 42 | /// 43 | public static readonly string TypeField = "_type"; 44 | 45 | /// 46 | /// Used by default build search hit, but requires a custom Index Modifier to set. 47 | /// 48 | public static readonly string SearchDescriptionField = "_vulcanSearchDescription"; 49 | 50 | /// 51 | /// Filters for classes that are not abstracts 52 | /// 53 | public static Func DefaultFilter = x => x.IsClass && !x.IsAbstract; 54 | 55 | /// 56 | /// Filters for types not abstracts 57 | /// 58 | public static Func AbstractFilter = x => !x.IsAbstract; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanContentHit.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | 6 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 7 | { 8 | /// 9 | /// Vulcan content hit 10 | /// 11 | public class VulcanContentHit : IVulcanContentHit 12 | { 13 | /// 14 | /// Content Guid 15 | /// 16 | public virtual Guid ContentGuid { get; set; } 17 | 18 | /// 19 | /// Episerver content reference 20 | /// 21 | public virtual ContentReference ContentLink { get; set; } 22 | 23 | /// 24 | /// Content type id 25 | /// 26 | public virtual int ContentTypeID { get; set; } 27 | 28 | /// 29 | /// Content is deleted 30 | /// 31 | public virtual bool IsDeleted { get; set; } 32 | 33 | /// 34 | /// Content name 35 | /// 36 | public virtual string Name { get; set; } 37 | 38 | /// 39 | /// Content parent reference 40 | /// 41 | public virtual ContentReference ParentLink { get; set; } 42 | 43 | /// 44 | /// Content properties 45 | /// 46 | public PropertyDataCollection Property { get; set; } 47 | 48 | /// 49 | /// Prices for commerce 50 | /// 51 | [EditorBrowsable(EditorBrowsableState.Never)] 52 | public Dictionary __prices { get; set; } 53 | 54 | /// 55 | /// Low prices for commerce 56 | /// 57 | [EditorBrowsable(EditorBrowsableState.Never)] 58 | public Dictionary __pricesLow { get; set; } 59 | 60 | /// 61 | /// High prices for commerce 62 | /// 63 | [EditorBrowsable(EditorBrowsableState.Never)] 64 | public Dictionary __pricesHigh { get; set; } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanApplicationSettings.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | 3 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 4 | { 5 | /// 6 | /// Default implementation of settings 7 | /// 8 | [ServiceConfiguration(typeof(IVulcanApplicationSettings), Lifecycle = ServiceInstanceScope.Singleton)] 9 | public class VulcanApplicationSettings : IVulcanApplicationSettings 10 | { 11 | /// 12 | /// Netframework constructor 13 | /// 14 | public VulcanApplicationSettings() 15 | { 16 | IsDebugMode = Internal.AppConfigurationHelper.IsDebugMode(); 17 | Url = Internal.AppConfigurationHelper.TryGetValueFromAppKey(key: "VulcanUrl"); 18 | IndexNamePrefix = Internal.AppConfigurationHelper.TryGetValueFromAppKey(key: "VulcanIndex"); 19 | Username = Internal.AppConfigurationHelper.TryGetValueFromAppKey(key: "VulcanUsername"); 20 | Password = Internal.AppConfigurationHelper.TryGetValueFromAppKey(key: "VulcanPassword"); 21 | EnableHttpCompression = Internal.AppConfigurationHelper.TryGetBoolFromKey(key: "VulcanEnableHttpCompression", defaultValue: false); 22 | } 23 | 24 | /// 25 | /// Is debug mode enabled 26 | /// 27 | public virtual bool IsDebugMode { get; } 28 | 29 | /// 30 | /// Elastic Search URL 31 | /// 32 | public virtual string Url { get; } 33 | 34 | /// 35 | /// Index Name prefix, ex web.Env 36 | /// 37 | public virtual string IndexNamePrefix { get; } 38 | 39 | /// 40 | /// Username to Elasticsearch connection if needed 41 | /// 42 | public virtual string Username { get; } 43 | 44 | /// 45 | /// Password to Elasticsearch connection if needed 46 | /// 47 | public virtual string Password { get; } 48 | 49 | /// 50 | /// Is http compression enabled 51 | /// 52 | public virtual bool EnableHttpCompression { get; } 53 | } 54 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/Controllers/VulcanApiController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Web.Mvc; 4 | using TcbInternetSolutions.Vulcan.Core; 5 | 6 | namespace TcbInternetSolutions.Vulcan.UI.Controllers 7 | { 8 | [Authorize(Roles="Administrators, WebAdmins, CmsAdmins, VulcanAdmins")] 9 | public class VulcanApiController : Base.BaseController 10 | { 11 | public VulcanApiController(IVulcanHandler vulcanHandler) : base(vulcanHandler) { } 12 | 13 | [HttpGet] 14 | public ActionResult ListSynonyms(string language) 15 | { 16 | var client = VulcanHandler.GetClient(string.IsNullOrWhiteSpace(language) ? CultureInfo.InvariantCulture : new CultureInfo(language)); 17 | 18 | return Json(client.GetSynonyms(), JsonRequestBehavior.AllowGet); 19 | } 20 | 21 | [HttpPost] 22 | public ActionResult AddSynonym(string language, string term, string synonyms, bool biDirectional) 23 | { 24 | if(string.IsNullOrWhiteSpace(term)) 25 | { 26 | throw new Exception($"AddSynonym: {nameof(term)} cannot be blank"); 27 | } 28 | 29 | if(string.IsNullOrWhiteSpace(synonyms)) 30 | { 31 | throw new Exception($"AddSynonym: {nameof(synonyms)} cannot be blank"); 32 | } 33 | 34 | var client = VulcanHandler.GetClient(string.IsNullOrWhiteSpace(language) ? CultureInfo.InvariantCulture : new CultureInfo(language)); 35 | client.AddSynonym(term, synonyms.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries), biDirectional); 36 | 37 | return Json("OK"); 38 | } 39 | 40 | [HttpPost] 41 | public ActionResult RemoveSynonym(string language, string term) 42 | { 43 | if (string.IsNullOrWhiteSpace(term)) 44 | { 45 | throw new Exception($"RemoveSynonym: {nameof(term)} cannot be blank"); 46 | } 47 | 48 | var client = VulcanHandler.GetClient(string.IsNullOrWhiteSpace(language) ? CultureInfo.InvariantCulture : new CultureInfo(language)); 49 | client.RemoveSynonym(term); 50 | 51 | return Json("OK"); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanPipelineSelector.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 2 | { 3 | using EPiServer.Core; 4 | using EPiServer.ServiceLocation; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | /// 9 | /// Default Pipeline Selector 10 | /// 11 | [ServiceConfiguration(typeof(IVulcanPipelineSelector), Lifecycle = ServiceInstanceScope.Singleton)] 12 | public class VulcanPipelineSelector : IVulcanPipelineSelector 13 | { 14 | private readonly IEnumerable _allPipelines; 15 | private IEnumerable _sortedPipelines; 16 | 17 | /// 18 | /// DI Constructor 19 | /// 20 | /// 21 | public VulcanPipelineSelector(IEnumerable allPipelines) 22 | { 23 | _allPipelines = allPipelines; 24 | } 25 | 26 | /// 27 | /// Tries to return pipeline for given Id 28 | /// 29 | /// 30 | /// 31 | public virtual IVulcanPipeline GetPipelineById(string id) 32 | { 33 | return string.IsNullOrWhiteSpace(id) 34 | ? null 35 | : GetSortedPipelines().FirstOrDefault(x => 36 | string.Compare(id, x.Id, System.StringComparison.OrdinalIgnoreCase) == 0); 37 | } 38 | 39 | /// 40 | /// Tries to determine if pipeline is a match for given content 41 | /// 42 | /// 43 | /// 44 | public virtual IVulcanPipeline GetPipelineForContent(IContent content) 45 | { 46 | return GetSortedPipelines()?.FirstOrDefault(x => x.IsMatch(content)); 47 | } 48 | 49 | private IEnumerable GetSortedPipelines() 50 | { 51 | if (_sortedPipelines == null && _allPipelines?.Any() == true) 52 | { 53 | _sortedPipelines = _allPipelines.OrderByDescending(x => x.SortOrder).ToList(); 54 | } 55 | 56 | return _sortedPipelines; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/Implementation/VulcanAttachmentInspector.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.ServiceLocation; 3 | using System; 4 | using System.Linq; 5 | using TcbInternetSolutions.Vulcan.Core.Extensions; 6 | 7 | namespace TcbInternetSolutions.Vulcan.AttachmentIndexer.Implementation 8 | { 9 | /// 10 | /// Determines if attachment can be indexed 11 | /// 12 | [ServiceConfiguration(typeof(IVulcanAttachmentInspector), Lifecycle = ServiceInstanceScope.Singleton)] 13 | public class VulcanAttachmentInspector : IVulcanAttachmentInspector 14 | { 15 | private readonly IVulcanAttachmentIndexerSettings _attachmentSettings; 16 | 17 | /// 18 | /// Constructor 19 | /// 20 | /// 21 | public VulcanAttachmentInspector(IVulcanAttachmentIndexerSettings attachmentSettings) 22 | { 23 | _attachmentSettings = attachmentSettings; 24 | } 25 | 26 | /// 27 | /// Determines if given mediadata is indexable 28 | /// 29 | /// 30 | /// 31 | public virtual bool AllowIndexing(MediaData media) 32 | { 33 | if (media == null || !_attachmentSettings.EnableAttachmentPlugins) 34 | return false; 35 | 36 | var allowed = true; 37 | var ext = media.SearchFileExtension(); 38 | 39 | if (!string.IsNullOrWhiteSpace(ext)) 40 | { 41 | allowed = _attachmentSettings?.SupportedFileExtensions?.Any(x => string.Compare(ext, x.Trim().TrimStart('.'), StringComparison.OrdinalIgnoreCase) == 0) == true; 42 | } 43 | 44 | if (!allowed || !_attachmentSettings.EnableFileSizeLimit) return allowed; 45 | long fileByteSize = -1; 46 | 47 | if (media.BinaryData != null) 48 | { 49 | using (var stream = media.BinaryData.OpenRead()) 50 | { 51 | fileByteSize = stream.Length; 52 | } 53 | } 54 | 55 | allowed = fileByteSize > 1 && _attachmentSettings.FileSizeLimit <= fileByteSize; 56 | 57 | return allowed; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed/GoogleProductFeedController.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | using System; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Web.Mvc; 6 | 7 | namespace TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed 8 | { 9 | public class GoogleProductFeedController : Controller 10 | { 11 | public Injected GoogleProductFeedService { get; set; } 12 | 13 | public ActionResult Feed(Type type, string market, string language, string currency, bool json = false) 14 | { 15 | var entries = GoogleProductFeedService.Service.GetFeed(type)?.GetEntries(market, language, currency); 16 | 17 | if (json) 18 | { 19 | return Json(entries, JsonRequestBehavior.AllowGet); // for debugging 20 | } 21 | 22 | const string format = "{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}\t{8}\t{9}\t{10}\t{11}\t{12}\t{13}\t{14}\t{15}"; 23 | 24 | var text = string.Format(format, "id", "title", "description", "link", "image_link", "availability", "price", "google_product_category", "brand", "gtin", "mpn", "identifier_exists", "condition", "adult", "shipping", "tax") + Environment.NewLine; 25 | 26 | if (entries != null && entries.Any()) 27 | { 28 | foreach(var entry in entries) 29 | { 30 | text += string.Format(format, 31 | entry.Id, 32 | entry.Title, 33 | entry.Description, 34 | entry.Link, 35 | entry.ImageLink, 36 | entry.Availability, 37 | entry.Price, 38 | entry.GoogleProductCategory, 39 | entry.Brand, 40 | entry.GTIN, 41 | entry.MPN, 42 | entry.IdentifierExists ? "yes" : "no", 43 | entry.Condition, 44 | entry.Adult, 45 | entry.Shipping, 46 | entry.Tax 47 | ) + Environment.NewLine; 48 | } 49 | } 50 | 51 | Response.Charset = "utf-8"; 52 | 53 | return File(Encoding.UTF8.GetBytes(text), "text"); 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce/VulcanCatalogSearchProvider.cs: -------------------------------------------------------------------------------- 1 | using EPiServer; 2 | using EPiServer.Commerce.Catalog.ContentTypes; 3 | using EPiServer.Core; 4 | using EPiServer.DataAbstraction; 5 | using EPiServer.Framework.Localization; 6 | using EPiServer.Shell; 7 | using EPiServer.Shell.Search; 8 | using EPiServer.Web; 9 | using TcbInternetSolutions.Vulcan.Core; 10 | using TcbInternetSolutions.Vulcan.Core.Implementation; 11 | using TcbInternetSolutions.Vulcan.Core.SearchProviders; 12 | using TcbInternetSolutions.Vulcan.Core.SearchProviders.Extensions; 13 | 14 | namespace TcbInternetSolutions.Vulcan.Commerce 15 | { 16 | [SearchProvider] 17 | public class VulcanCatalogSearchProvider : VulcanSearchProviderBase 18 | { 19 | public VulcanCatalogSearchProvider() 20 | : this( 21 | VulcanHelper.GetService(), 22 | VulcanHelper.GetService(), 23 | VulcanHelper.GetService(), 24 | VulcanHelper.GetService(), 25 | VulcanHelper.GetService(), 26 | VulcanHelper.GetService() 27 | ) 28 | { } 29 | 30 | public VulcanCatalogSearchProvider( 31 | IVulcanHandler vulcanHandler, 32 | LocalizationService localizationService, 33 | ISiteDefinitionResolver siteDefinitionResolver, 34 | IContentRepository contentRepository, 35 | IContentTypeRepository contentTypeRepository, 36 | UIDescriptorRegistry uiDescriptorRegistry 37 | ) 38 | : base(vulcanHandler, contentRepository, contentTypeRepository, localizationService, uiDescriptorRegistry, siteDefinitionResolver) 39 | { 40 | EditPath = GetEditPath; 41 | } 42 | 43 | public override string Area => "Commerce/Catalog"; 44 | 45 | public override string Category => LocalizationService.GetString("/vulcan/searchprovider/products/name"); 46 | 47 | protected override string IconCssClass(IContent contentData) => "epi-resourceIcon epi-resourceIcon-page"; 48 | 49 | private static string GetEditPath(IContent entryContent, ContentReference contentLink, string languageName) 50 | { 51 | return entryContent.GetUri(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanSearchHitList.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 2 | { 3 | using EPiServer.Core; 4 | using Nest; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | /// 9 | /// Vulcan search hit list 10 | /// 11 | public class VulcanSearchHitList 12 | { 13 | private long _took; 14 | 15 | private List _results; 16 | 17 | /// 18 | /// Constructor 19 | /// 20 | public VulcanSearchHitList() : this(new List()) { } 21 | 22 | /// 23 | /// Constructor 24 | /// 25 | /// 26 | public VulcanSearchHitList(IEnumerable results) 27 | { 28 | _took = -1; 29 | _results = results?.ToList(); 30 | } 31 | 32 | /// 33 | /// Vulcan response context 34 | /// 35 | public virtual ISearchResponse ResponseContext { get; set; } 36 | 37 | /// 38 | /// Found items 39 | /// 40 | public virtual List Items 41 | { 42 | get => _results; 43 | set => _results = value; 44 | } 45 | 46 | /// 47 | /// Page 48 | /// 49 | public virtual int Page { get; set; } 50 | 51 | /// 52 | /// Pagesize 53 | /// 54 | public virtual int PageSize { get; set; } 55 | 56 | /// 57 | /// Total items 58 | /// 59 | public virtual long TotalHits { get; set; } 60 | 61 | /// 62 | /// Search time 63 | /// 64 | public virtual long Took 65 | { 66 | get 67 | { 68 | if (_took < -1 && ResponseContext != null) 69 | { 70 | #if NEST2 71 | _took = ResponseContext.TookAsLong; 72 | #elif NEST5 73 | _took = ResponseContext.Took; 74 | #endif 75 | } 76 | 77 | return _took; 78 | } 79 | set 80 | { 81 | _took = value; 82 | } 83 | 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core.SearchProviders/TcbInternetSolutions.Vulcan.Core.SearchProviders.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | net461; 10 | TcbInternetSolutions.Vulcan.Core.SearchProviders 11 | Dan Matthews, Brad McDavid 12 | Copyright $(CurrentYear) 13 | Core Search Provider support for Vulcan, the lightweight Elasticsearch client for Episerver 14 | 15 | 6.0.0 16 | $(PackageVersion)-$(VersionSuffix) 17 | 18 | 19 | ElasticSearch Nest 20 | 21 | 22 | false 23 | 24 | 25 | true 26 | 27 | 28 | true 29 | true 30 | 31 | 32 | Commit: $(RepositoryCommit) Branch: $(RepositoryBranch) Build: $(CIBuildNumber) 33 | $(PackageVersion) $(BuildInfo) 34 | 35 | 36 | 4.0.0.0 37 | 4.0.0.0 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Internal/AppConfigurationHelper.cs: -------------------------------------------------------------------------------- 1 | #if NET461 2 | using System.Configuration; 3 | #endif 4 | 5 | namespace TcbInternetSolutions.Vulcan.Core.Internal 6 | { 7 | /// 8 | /// Netframework helper 9 | /// 10 | public static class AppConfigurationHelper 11 | { 12 | /// 13 | /// Netframework app key converter, if net core default value is returned 14 | /// 15 | /// 16 | /// 17 | /// 18 | public static bool TryGetBoolFromKey(string key, bool defaultValue) 19 | { 20 | 21 | var keyValue = TryGetValueFromAppKey(key); 22 | if (string.IsNullOrWhiteSpace(keyValue)) return defaultValue; 23 | 24 | return bool.TryParse(keyValue, out var converted) ? 25 | converted : 26 | GetSetting(keyValue, defaultValue); 27 | } 28 | 29 | /// 30 | /// Netframework app key getter, netcore returns default value 31 | /// 32 | /// 33 | /// 34 | /// 35 | public static string TryGetValueFromAppKey(string key, string defaultValue = null) 36 | { 37 | #if NET461 38 | var readValue = ConfigurationManager.AppSettings[key]; 39 | return string.IsNullOrWhiteSpace(readValue) ? defaultValue : readValue; 40 | #else 41 | return defaultValue; 42 | 43 | #endif 44 | } 45 | 46 | /// 47 | /// Netframework debug mode check, always false for netstandard 48 | /// 49 | /// 50 | public static bool IsDebugMode() 51 | { 52 | #if NET461 53 | var section = ConfigurationManager.GetSection("system.web/compilation") as System.Web.Configuration.CompilationSection; 54 | return section?.Debug ?? false; 55 | #else 56 | return false; 57 | #endif 58 | } 59 | 60 | // ReSharper disable once UnusedMember.Local 61 | private static bool GetSetting(string setting, bool defaultValue) 62 | { 63 | if (string.IsNullOrWhiteSpace(setting)) 64 | { 65 | return defaultValue; 66 | } 67 | 68 | return setting.Equals(value: "true") || setting.Equals(value: "1"); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed/TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 1701;1702;1705;CS1591 10 | net461; 11 | TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed 12 | Dan Matthews 13 | Copyright $(CurrentYear) 14 | Google Product Feed support for Vulcan, the lightweight Elasticsearch client for Episerver 15 | 16 | 6.0.0 17 | $(PackageVersion)-$(VersionSuffix) 18 | 19 | 20 | ElasticSearch Nest Commerce Google 21 | 22 | 23 | false 24 | 25 | 26 | true 27 | 28 | 29 | true 30 | true 31 | 32 | 33 | Commit: $(RepositoryCommit) Branch: $(RepositoryBranch) Build: $(CIBuildNumber) 34 | $(PackageVersion) $(BuildInfo) 35 | 36 | 37 | 4.0.0.0 38 | 4.0.0.0 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/TcbInternetSolutions.Vulcan.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $(DefineConstants);NEST5 10 | net461;netstandard2.0 11 | TcbInternetSolutions.Vulcan.Core 12 | Dan Matthews, Brad McDavid 13 | Copyright $(CurrentYear) 14 | Vulcan, the lightweight Elasticsearch client for Episerver 15 | 16 | 6.0.0 17 | $(PackageVersion)-$(VersionSuffix) 18 | 19 | 20 | ElasticSearch Nest Search 21 | 22 | 23 | true 24 | 25 | 26 | true 27 | 28 | 29 | true 30 | true 31 | 32 | 33 | Commit: $(RepositoryCommit) Branch: $(RepositoryBranch) Build: $(CIBuildNumber) 34 | $(PackageVersion) $(BuildInfo) 35 | 36 | 37 | 4.0.0.0 38 | 4.0.0.0 39 | 40 | 41 | 42 | full 43 | true 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/TcbInternetSolutions.Vulcan.AttachmentIndexer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $(DefineConstants);NEST5 10 | net461;netstandard2.0 11 | TcbInternetSolutions.Vulcan.AttachmentIndexer 12 | Dan Matthews, Brad McDavid 13 | Copyright $(CurrentYear) 14 | Support for indexing attachments 15 | 16 | 6.0.0 17 | $(PackageVersion)-$(VersionSuffix) 18 | 19 | 20 | ElasticSearch Nest Attachments Files Media 21 | 22 | 23 | true 24 | 25 | 26 | true 27 | 28 | 29 | true 30 | true 31 | 32 | 33 | Commit: $(RepositoryCommit) Branch: $(RepositoryBranch) Build: $(CIBuildNumber) 34 | $(PackageVersion) $(BuildInfo) 35 | 36 | 37 | 4.0.0.0 38 | 4.0.0.0 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Extensions/FieldExtensions.cs: -------------------------------------------------------------------------------- 1 | using Nest; 2 | using Newtonsoft.Json.Serialization; 3 | using System; 4 | using System.Linq.Expressions; 5 | 6 | namespace TcbInternetSolutions.Vulcan.Core.Extensions 7 | { 8 | /// 9 | /// Field Extensions 10 | /// 11 | public static class FieldExtensions 12 | { 13 | internal static DefaultContractResolver FallbackNameResolver = new CamelCasePropertyNamesContractResolver(); 14 | 15 | /// 16 | /// Creates field search for all analyzed fields. 17 | /// 18 | /// 19 | /// 20 | /// 21 | public static FieldsDescriptor AllAnalyzed(this FieldsDescriptor descriptor) where T : class => 22 | descriptor.Field($"*.{VulcanFieldConstants.AnalyzedModifier}"); 23 | 24 | /// 25 | /// Creates analyzed field descriptor from given object's property name 26 | /// 27 | /// 28 | /// 29 | /// 30 | /// 31 | /// CamelCasePropertyNamesContractResolver by default 32 | /// 33 | [Obsolete("Please use f => f.PropertyName.Suffix(VulcanFieldConstants.AnalyzedModifier) instead.", false)] 34 | public static FieldsDescriptor FieldAnalyzed(this FieldsDescriptor descriptor, Expression> field, double? boost = null, DefaultContractResolver resolver = null) where T : class 35 | { 36 | MemberExpression memberExpression = null; 37 | 38 | switch (field.Body.NodeType) 39 | { 40 | case ExpressionType.Convert: 41 | memberExpression = ((UnaryExpression)field.Body).Operand as MemberExpression; 42 | break; 43 | case ExpressionType.MemberAccess: 44 | memberExpression = field.Body as MemberExpression; 45 | break; 46 | } 47 | 48 | if (memberExpression == null) return descriptor; 49 | 50 | resolver = resolver ?? FallbackNameResolver; 51 | var name = resolver.GetResolvedPropertyName(memberExpression.Member.Name); 52 | 53 | return descriptor.Field(memberExpression.Type == typeof(string) ? $"{name}.{VulcanFieldConstants.AnalyzedModifier}" : name, boost); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/readme.md: -------------------------------------------------------------------------------- 1 | # TcbInternetSolutions.Vulcan.AttachmentIndexer Read Me 2 | 3 | ## Support for elasticsearch mapper-attachments 4 | 5 | By default 'mapper-attachments' is required and enabled via the interface 'IVulcanAttachmentIndexerSettings'. The default implementation uses the 6 | appsetting key 'VulcanIndexAttachmentPluginsEnabled' to determine if its enabled. 7 | 8 | ## Support for media data parsing in Episerver 9 | 10 | Media data can now be passed to Elasticsearch as raw text, but an interface must be implemented and registered to Episerver's service locator. 11 | 12 | ### Tika media parser 13 | 14 | ```cs 15 | /// 16 | /// Requires TikaOnDotnet.TextExtractor to extract PDF,Doc(x), PPT(X), and XLS(X) documents 17 | /// 18 | [ServiceConfiguration(typeof(IVulcanBytesToStringConverter), Lifecycle = ServiceInstanceScope.Singleton)] 19 | public class TikaBytesToStringConverter : IVulcanBytesToStringConverter 20 | { 21 | public string ConvertToString(byte[] bytes, string mimeType) 22 | { 23 | // using TikaOnDotNet.TextExtraction 24 | return new TikaOnDotNet.TextExtraction.TextExtractor().Extract(bytes).Text?.Trim(); 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | ### iTextSharp pdf media parser 31 | 32 | ```cs 33 | /// 34 | /// Requires a NuGet package iTextSharp for PDF files only 35 | /// 36 | [ServiceConfiguration(typeof(IVulcanBytesToStringConverter), Lifecycle = ServiceInstanceScope.Singleton)] 37 | public class PdfBytesToStringConverter : IVulcanBytesToStringConverter 38 | { 39 | public string ConvertToString(byte[] bytes, string mimeType) 40 | { 41 | var mType = mimeType.ToLowerInvariant(); 42 | 43 | if (mType == "application/pdf") 44 | { 45 | using (var reader = new iTextSharp.text.pdf.PdfReader(bytes)) 46 | { 47 | StringBuilder s = new StringBuilder(); 48 | var parserStrategy = new iTextSharp.text.pdf.parser.SimpleTextExtractionStrategy(); 49 | var totalPages = reader.NumberOfPages; 50 | 51 | for (int i = 1; i <= totalPages; i++) 52 | { 53 | s.AppendFormat(" {0}", iTextSharp.text.pdf.parser.PdfTextExtractor.GetTextFromPage(reader, i, parserStrategy)); 54 | } 55 | 56 | return s.ToString().Trim(); 57 | } 58 | } 59 | 60 | return null; 61 | } 62 | } 63 | } 64 | ``` -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce/TcbInternetSolutions.Vulcan.Commerce.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 1701;1702;1705;CS1591 10 | net461; 11 | TcbInternetSolutions.Vulcan.Commerce 12 | Dan Matthews, Brad McDavid 13 | Copyright $(CurrentYear) 14 | Commerce support for Vulcan, the lightweight Elasticsearch client for Episerver 15 | 16 | 6.0.0 17 | $(PackageVersion)-$(VersionSuffix) 18 | 19 | 20 | ElasticSearch Nest Commerce 21 | 22 | 23 | false 24 | 25 | 26 | true 27 | 28 | 29 | true 30 | true 31 | 32 | 33 | Commit: $(RepositoryCommit) Branch: $(RepositoryBranch) Build: $(CIBuildNumber) 34 | $(PackageVersion) $(BuildInfo) 35 | 36 | 37 | 4.0.0.0 38 | 4.0.0.0 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce.SearchProviders/TcbInternetSolutions.Vulcan.Commerce.SearchProviders.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 1701;1702;1705;CS1591 10 | net461; 11 | TcbInternetSolutions.Vulcan.Commerce.SearchProviders 12 | Dan Matthews, Brad McDavid 13 | Copyright $(CurrentYear) 14 | Commerce Search Provider support for Vulcan, the lightweight Elasticsearch client for Episerver 15 | 16 | 6.0.0 17 | $(PackageVersion)-$(VersionSuffix) 18 | 19 | 20 | ElasticSearch Nest Commerce 21 | 22 | 23 | false 24 | 25 | 26 | true 27 | 28 | 29 | true 30 | true 31 | 32 | 33 | Commit: $(RepositoryCommit) Branch: $(RepositoryBranch) Build: $(CIBuildNumber) 34 | $(PackageVersion) $(BuildInfo) 35 | 36 | 37 | 4.0.0.0 38 | 4.0.0.0 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanCmsIndexer.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework.Cache; 2 | using EPiServer.Web; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using TcbInternetSolutions.Vulcan.Core.Internal; 6 | 7 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 8 | { 9 | /// 10 | /// Default CMS content indexer 11 | /// 12 | public class VulcanCmsIndexer : IVulcanContentIndexerWithCacheClearing 13 | { 14 | private readonly ISynchronizedObjectInstanceCache _synchronizedObjectInstanceCache; 15 | 16 | /// 17 | /// DI Constructor 18 | /// 19 | /// 20 | public VulcanCmsIndexer(ISynchronizedObjectInstanceCache synchronizedObjectInstanceCache) 21 | { 22 | _synchronizedObjectInstanceCache = synchronizedObjectInstanceCache; 23 | } 24 | 25 | /// 26 | /// Default cache clear interval 27 | /// 28 | public int ClearCacheItemInterval => 100; 29 | 30 | /// 31 | /// Indexer Name 32 | /// 33 | public string IndexerName => "CMS Content"; 34 | 35 | /// 36 | /// Default clears Episerver cache manager cache. 37 | /// 38 | public void ClearCache() 39 | { 40 | //CacheManager.Clear(); //this has been deprecated 41 | var cacheKeys = GetCacheKeys(); 42 | 43 | foreach(var key in cacheKeys) 44 | { 45 | _synchronizedObjectInstanceCache.RemoveLocal(key); 46 | _synchronizedObjectInstanceCache.RemoveRemote(key); 47 | } 48 | } 49 | 50 | /// 51 | /// Indexer root 52 | /// 53 | /// 54 | public virtual KeyValuePair GetRoot() => 55 | new KeyValuePair(SiteDefinition.Current.RootPage, "CMS"); 56 | 57 | private static IEnumerable GetCacheKeys() 58 | { 59 | #if NET461 60 | var cacheKeys = new List(); 61 | var enumerator = System.Web.HttpRuntime.Cache.GetEnumerator(); 62 | 63 | while (enumerator.MoveNext()) 64 | { 65 | var key = enumerator.Key?.ToString() ?? string.Empty; 66 | cacheKeys.Add(key); 67 | } 68 | 69 | return cacheKeys; 70 | #else 71 | //todo: figure out netstandard alternative 72 | return Enumerable.Empty(); 73 | #endif 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/TcbInternetSolutions.Vulcan.UI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $(DefineConstants);NEST5 10 | 1701;1702;1705;CS1591 11 | net461; 12 | TcbInternetSolutions.Vulcan.UI 13 | Dan Matthews, Brad McDavid 14 | Copyright $(CurrentYear) 15 | User Interface for Vulcan, the lightweight Elasticsearch client for Episerver 16 | 17 | 6.0.0 18 | $(PackageVersion)-$(VersionSuffix) 19 | 20 | 21 | Vulcan UI Management ElasticSearch 22 | 23 | 24 | true 25 | 26 | 27 | true 28 | 29 | 30 | true 31 | true 32 | 33 | 34 | Commit: $(RepositoryCommit) Branch: $(RepositoryBranch) Build: $(CIBuildNumber) 35 | $(PackageVersion) $(BuildInfo) 36 | 37 | 38 | 4.0.0.0 39 | 4.0.0.0 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Extensions/UserExtensions.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Security; 2 | using EPiServer.ServiceLocation; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Security.Principal; 7 | 8 | namespace TcbInternetSolutions.Vulcan.Core.Extensions 9 | { 10 | /// 11 | /// User extensions 12 | /// 13 | public static class UserExtensions 14 | { 15 | /// 16 | /// IVirtualRoleRepository dependency 17 | /// 18 | public static Injected VirtualRoleRepository { get; set; } 19 | 20 | /// 21 | /// IUserImpersonation dependency 22 | /// 23 | public static Injected UserImpersonation { get; set; } 24 | 25 | /// 26 | /// IPrincipalAccessor 27 | /// 28 | public static Injected PrincipalAccessor { get; set; } 29 | 30 | /// 31 | /// Gets current principal 32 | /// 33 | /// 34 | public static IPrincipal GetUser() => PrincipalAccessor.Service.Principal; 35 | 36 | /// 37 | /// Gets principal from username 38 | /// 39 | /// 40 | /// 41 | public static IPrincipal GetUser(string username) => UserImpersonation.Service.CreatePrincipal(username); 42 | 43 | /// 44 | /// Gets roles for given principle 45 | /// 46 | /// 47 | /// 48 | public static IEnumerable GetRoles(this IPrincipal principle) 49 | { 50 | if (principle == null) 51 | throw new ArgumentNullException(nameof(principle)); 52 | 53 | // todo: is PrincipalInfo needed for roleslist 54 | //var userPrinciple = new PrincipalInfo(principle); 55 | var list = new List(); 56 | 57 | foreach (var name in VirtualRoleRepository.Service.GetAllRoles()) 58 | { 59 | if (VirtualRoleRepository.Service.TryGetRole(name, out var virtualRoleProvider) && virtualRoleProvider.IsInVirtualRole(principle, null)) 60 | { 61 | list.Add(name); 62 | } 63 | } 64 | //todo: figure out netstandard equivalent 65 | #if NET461 66 | if (System.Web.Security.Roles.Enabled) 67 | { 68 | list.AddRange(System.Web.Security.Roles.GetRolesForUser(principle.Identity.Name)); 69 | } 70 | #endif 71 | return list.Distinct(); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/Implementation/VulcanAttachmentIndexerSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.AttachmentIndexer.Implementation 2 | { 3 | using EPiServer.ServiceLocation; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// Default attachment indexer settings 9 | /// 10 | [ServiceConfiguration(typeof(IVulcanAttachmentIndexerSettings), Lifecycle = ServiceInstanceScope.Singleton)] 11 | public class VulcanAttachmentIndexerSettings : IVulcanAttachmentIndexerSettings 12 | { 13 | private readonly string _supportedExtensionsAsString; 14 | private IEnumerable _supportedFileExtensions; 15 | 16 | /// 17 | /// Constructor 18 | /// 19 | public VulcanAttachmentIndexerSettings() 20 | { 21 | _supportedExtensionsAsString = Core.Internal.AppConfigurationHelper.TryGetValueFromAppKey(key: "VulcanIndexAttachmentFileExtensions"); 22 | 23 | EnableAttachmentPlugins = 24 | Core.Internal.AppConfigurationHelper.TryGetBoolFromKey(key: "VulcanIndexAttachmentPluginsEnabled", defaultValue: true); 25 | 26 | EnableFileSizeLimit = 27 | Core.Internal.AppConfigurationHelper.TryGetBoolFromKey(key: "VulcanIndexAttachmentFileLimitEnabled", defaultValue: false); 28 | } 29 | 30 | /// 31 | /// Determines if Elasticsearch has plugins to handle base64 data 32 | /// 33 | public virtual bool EnableAttachmentPlugins { get; } 34 | 35 | /// 36 | /// Determines if file size are considered for indexing. 37 | /// 38 | public virtual bool EnableFileSizeLimit { get; } 39 | 40 | /// 41 | /// Default max file size limit is 15 MB 42 | /// 43 | public virtual long FileSizeLimit => 15000000; 44 | 45 | /// 46 | /// Supported file extensions, by default is pdf,doc,docx,xls,xlsx,ppt,pptx,txt,rtf 47 | /// 48 | public virtual IEnumerable SupportedFileExtensions 49 | { 50 | get 51 | { 52 | if (_supportedFileExtensions != null) return _supportedFileExtensions; 53 | 54 | var allowedExtensions = _supportedExtensionsAsString; 55 | 56 | _supportedFileExtensions = string.IsNullOrWhiteSpace(allowedExtensions) ? 57 | new[] { "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "rtf" } : 58 | allowedExtensions.Split(new[] { ',', '|', ';' }, StringSplitOptions.RemoveEmptyEntries); 59 | 60 | return _supportedFileExtensions; 61 | } 62 | } 63 | 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanClient.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using Nest; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.Security.Principal; 7 | 8 | namespace TcbInternetSolutions.Vulcan.Core 9 | { 10 | /// 11 | /// Vulcan client contract 12 | /// 13 | public interface IVulcanClient : IElasticClient 14 | { 15 | /// 16 | /// Search content 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | ISearchResponse SearchContent(Func, SearchDescriptor> searchDescriptor = null, 26 | bool includeNeutralLanguage = false, 27 | IEnumerable rootReferences = null, 28 | IEnumerable typeFilter = null, 29 | IPrincipal principleReadFilter = null) where T : class, IContent; 30 | 31 | /// 32 | /// Index content 33 | /// 34 | /// 35 | void IndexContent(IContent content); 36 | 37 | /// 38 | /// Delete content 39 | /// 40 | /// 41 | void DeleteContent(IContent content); 42 | 43 | /// 44 | /// Delete content 45 | /// 46 | /// 47 | /// 48 | void DeleteContent(ContentReference contentLink, string typeName); 49 | 50 | /// 51 | /// Index name 52 | /// 53 | string IndexName { get; } 54 | 55 | /// 56 | /// client language 57 | /// 58 | CultureInfo Language { get; } 59 | 60 | /// 61 | /// Add synonym 62 | /// 63 | /// 64 | /// 65 | /// 66 | void AddSynonym(string term, string [] synonyms, bool biDirectional); 67 | 68 | /// 69 | /// Delete synonym 70 | /// 71 | /// 72 | void RemoveSynonym(string term); 73 | 74 | /// 75 | /// Get synonyms 76 | /// 77 | /// 78 | Dictionary> GetSynonyms(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/TcbInternetSolutions.Vulcan.UI/Views/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanMediaReader.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 2 | { 3 | using EPiServer.Core; 4 | using EPiServer.ServiceLocation; 5 | using System; 6 | 7 | /// 8 | /// Converts media data to byte array 9 | /// 10 | [ServiceConfiguration(typeof(IVulcanMediaReader), Lifecycle = ServiceInstanceScope.Singleton)] 11 | public class VulcanMediaReader : IVulcanMediaReader 12 | { 13 | /// 14 | /// Reads complete media data to byte array 15 | /// 16 | /// 17 | /// 18 | public virtual byte[] ReadToEnd(MediaData media) 19 | { 20 | if (media?.BinaryData == null) return null; 21 | byte[] bytes; 22 | 23 | using (var s = media.BinaryData.OpenRead()) 24 | { 25 | bytes = ReadToEnd(s); 26 | } 27 | 28 | return bytes; 29 | } 30 | 31 | /// 32 | /// Reads full stream to byte array 33 | /// 34 | /// 35 | /// 36 | protected static byte[] ReadToEnd(System.IO.Stream stream) 37 | { 38 | long originalPosition = 0; 39 | 40 | if (stream.CanSeek) 41 | { 42 | originalPosition = stream.Position; 43 | stream.Position = 0; 44 | } 45 | 46 | try 47 | { 48 | var readBuffer = new byte[4096]; 49 | var totalBytesRead = 0; 50 | int bytesRead; 51 | 52 | while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0) 53 | { 54 | totalBytesRead += bytesRead; 55 | 56 | if (totalBytesRead != readBuffer.Length) continue; 57 | 58 | var nextByte = stream.ReadByte(); 59 | if (nextByte == -1) continue; 60 | 61 | 62 | var temp = new byte[readBuffer.Length * 2]; 63 | Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length); 64 | Buffer.SetByte(temp, totalBytesRead, (byte)nextByte); 65 | readBuffer = temp; 66 | totalBytesRead++; 67 | } 68 | 69 | var buffer = readBuffer; 70 | 71 | if (readBuffer.Length == totalBytesRead) return buffer; 72 | 73 | buffer = new byte[totalBytesRead]; 74 | Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead); 75 | 76 | return buffer; 77 | } 78 | finally 79 | { 80 | if (stream.CanSeek) 81 | { 82 | stream.Position = originalPosition; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/Converters/ContentReferenceConverter.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using Newtonsoft.Json; 3 | using System; 4 | 5 | namespace TcbInternetSolutions.Vulcan.Core.Implementation.Converters 6 | { 7 | /// 8 | /// Converts content reference properties to use references without work ID/version set 9 | /// 10 | public class ContentReferenceConverter : JsonConverter 11 | { 12 | private static readonly Type ContentReferenceType = typeof(ContentReference); 13 | 14 | /// 15 | /// Can read, default is true 16 | /// 17 | public override bool CanRead { get; } = true; 18 | 19 | /// 20 | /// Can write, default is true 21 | /// 22 | public override bool CanWrite { get; } = true; 23 | 24 | /// 25 | /// Determines if convert supports given type 26 | /// 27 | /// 28 | /// 29 | public override bool CanConvert(Type objectType) 30 | { 31 | return ContentReferenceType.IsAssignableFrom(objectType); 32 | } 33 | 34 | /// 35 | /// Creates content reference from value 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// 41 | /// 42 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 43 | { 44 | switch (reader.TokenType) 45 | { 46 | case JsonToken.Null: 47 | return null; 48 | case JsonToken.String: 49 | if (reader.Value == null || string.IsNullOrEmpty(reader.Value.ToString())) 50 | { 51 | return null; 52 | } 53 | return new ContentReference((string)reader.Value); 54 | case JsonToken.Integer: 55 | return new ContentReference((int)reader.Value); 56 | } 57 | 58 | throw new JsonSerializationException($"Cannot convert token of type {reader.TokenType} to {objectType}."); 59 | } 60 | 61 | /// 62 | /// Writes value without work ID/version 63 | /// 64 | /// 65 | /// 66 | /// 67 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 68 | { 69 | var contentRef = value as ContentReference; 70 | 71 | if (contentRef == null) 72 | writer.WriteNull(); 73 | else 74 | writer.WriteValue(contentRef.ToReferenceWithoutVersion().ToString()); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed/GoogleProductFeedService.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Commerce.Catalog.ContentTypes; 2 | using EPiServer.ServiceLocation; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Web.Mvc; 6 | using System.Web.Routing; 7 | 8 | namespace TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed 9 | { 10 | [ServiceConfiguration(ServiceType = typeof(IGoogleProductFeedService), Lifecycle = ServiceInstanceScope.Singleton)] 11 | public class GoogleProductFeedService : IGoogleProductFeedService 12 | { 13 | private Dictionary Feeds { get; } = new Dictionary(); 14 | 15 | public IGoogleProductFeed CreateFeed(string urlSegment) where T : VariationContent 16 | { 17 | return CreateFeed, T>(urlSegment); // use default concrete implementation 18 | } 19 | 20 | public IGoogleProductFeed CreateFeed(string urlSegment) where TGoogleProductFeed : IGoogleProductFeed, new() where TVariationContent : VariationContent 21 | { 22 | if(string.IsNullOrWhiteSpace(urlSegment)) 23 | { 24 | throw new ArgumentException("Url segment must be set to add a feed"); 25 | } 26 | 27 | if (Feeds.ContainsKey(typeof(TVariationContent))) 28 | { 29 | if(Feeds[typeof(TVariationContent)].UrlSegment != urlSegment) 30 | { 31 | throw new ArgumentException("That feed was already added for type " + typeof(TVariationContent).FullName + " with a different url segment"); 32 | } 33 | 34 | return Feeds[typeof(TVariationContent)] as IGoogleProductFeed; 35 | } 36 | 37 | var url = urlSegment; 38 | 39 | if (!url.StartsWith("/")) url = "/" + url; 40 | if (!url.EndsWith("/")) url += "/"; 41 | 42 | url = "GoogleProductFeed" + url + "{market}/{language}/{currency}"; 43 | 44 | var route = RouteTable.Routes.MapRoute( 45 | "GoogleProductFeed " + typeof(TVariationContent).FullName, 46 | url, 47 | new { controller = "GoogleProductFeed", action = "Feed", type = typeof(TVariationContent), market = "", language = "", currency = "" }, 48 | new[] { "TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed" }); 49 | 50 | var feed = new TGoogleProductFeed 51 | { 52 | Route = route, 53 | UrlSegment = urlSegment 54 | }; 55 | 56 | Feeds.Add(typeof(TVariationContent), feed); 57 | 58 | return feed; 59 | } 60 | 61 | public IGoogleProductFeed GetFeed() where T : VariationContent 62 | { 63 | return Feeds.ContainsKey(typeof(T)) ? Feeds[typeof(T)] as IGoogleProductFeed : null; 64 | } 65 | 66 | public IGoogleProductFeed GetFeed(Type type) 67 | { 68 | return Feeds.ContainsKey(type) ? Feeds[type] : null; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce/VulcanPriceReindexTrigger.cs: -------------------------------------------------------------------------------- 1 | using EPiServer; 2 | using EPiServer.Commerce.Catalog.ContentTypes; 3 | using EPiServer.Commerce.Extensions; 4 | using EPiServer.Core; 5 | using EPiServer.Framework; 6 | using EPiServer.Framework.Initialization; 7 | using Mediachase.Commerce.Catalog; 8 | using Mediachase.Commerce.Engine.Events; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using TcbInternetSolutions.Vulcan.Core; 12 | 13 | namespace TcbInternetSolutions.Vulcan.Commerce 14 | { 15 | [InitializableModule] 16 | [ModuleDependency(typeof(EPiServer.Web.InitializationModule))] 17 | public class VulcanPriceReindexTrigger : IInitializableModule 18 | { 19 | private IVulcanHandler _vulcanHandler; 20 | private ReferenceConverter _referenceConverter; 21 | private IContentLoader _contentLoader; 22 | 23 | public void Initialize(InitializationEngine context) 24 | { 25 | var broadcaster = context.Locate.Advanced.GetInstance(); 26 | _contentLoader = context.Locate.ContentLoader(); 27 | _referenceConverter = context.Locate.ReferenceConverter(); 28 | _vulcanHandler = context.Locate.Advanced.GetInstance(); 29 | 30 | broadcaster.InventoryUpdated += Broadcaster_InventoryUpdated; 31 | broadcaster.PriceUpdated += Broadcaster_PriceUpdated; 32 | } 33 | 34 | private void Broadcaster_PriceUpdated(object sender, PriceUpdateEventArgs e) 35 | { 36 | ReindexVariations(e.CatalogKeys.Select(ck => ck.CatalogEntryCode)); 37 | } 38 | 39 | private void Broadcaster_InventoryUpdated(object sender, InventoryUpdateEventArgs e) 40 | { 41 | ReindexVariations(e.CatalogKeys.Select(ck => ck.CatalogEntryCode)); 42 | } 43 | 44 | private void ReindexVariations(IEnumerable variantCodes) 45 | { 46 | var clients = _vulcanHandler.GetClients(); 47 | if (clients?.Any() != true) return; 48 | 49 | var codes = variantCodes as IList ?? variantCodes.ToList(); 50 | foreach (var client in clients) 51 | { 52 | foreach (var variantCode in codes) 53 | { 54 | var variantLink = _referenceConverter.GetContentLink(variantCode); 55 | 56 | if (ContentReference.IsNullOrEmpty(variantLink)) continue; 57 | 58 | var variant = _contentLoader.Get(variantLink, client.Language); 59 | 60 | if (variant == null) continue; 61 | 62 | var existing = client.SearchContent(s => s.Query(q => q.Term(v => v.Code, variantCode))); 63 | 64 | if (existing.Total > 0) 65 | { 66 | client.IndexContent(variant); 67 | } 68 | } 69 | } 70 | } 71 | 72 | public void Uninitialize(InitializationEngine context) 73 | { 74 | var broadcaster = context.Locate.Advanced.GetInstance(); 75 | 76 | broadcaster.InventoryUpdated -= Broadcaster_InventoryUpdated; 77 | broadcaster.PriceUpdated -= Broadcaster_PriceUpdated; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core.SearchProviders/VulcanPageSearchProvider.cs: -------------------------------------------------------------------------------- 1 | using EPiServer; 2 | using EPiServer.Core; 3 | using EPiServer.DataAbstraction; 4 | using EPiServer.Framework.Localization; 5 | using EPiServer.Shell; 6 | using EPiServer.Shell.Search; 7 | using EPiServer.Web; 8 | using TcbInternetSolutions.Vulcan.Core.Implementation; 9 | 10 | namespace TcbInternetSolutions.Vulcan.Core.SearchProviders 11 | { 12 | 13 | /// 14 | /// UI search provider for PageData 15 | /// 16 | [SearchProvider] 17 | public class VulcanPageSearchProvider : VulcanSearchProviderBase 18 | { 19 | /// 20 | /// Constructor 21 | /// 22 | public VulcanPageSearchProvider() 23 | : this( 24 | VulcanHelper.GetService(), 25 | VulcanHelper.GetService(), 26 | VulcanHelper.GetService(), 27 | VulcanHelper.GetService(), 28 | VulcanHelper.GetService(), 29 | VulcanHelper.GetService() 30 | ) 31 | { } 32 | 33 | /// 34 | /// Injected contructor 35 | /// 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// 41 | /// 42 | public VulcanPageSearchProvider( 43 | IVulcanHandler vulcanHandler, 44 | LocalizationService localizationService, 45 | ISiteDefinitionResolver siteDefintionResolver, 46 | IContentRepository contentRepository, 47 | IContentTypeRepository contentTypeRepository, 48 | UIDescriptorRegistry uiDescriptorRegistry 49 | ) 50 | : base(vulcanHandler, contentRepository, contentTypeRepository, localizationService, uiDescriptorRegistry, siteDefintionResolver) 51 | { 52 | 53 | } 54 | 55 | /// 56 | /// Area that the provider maps to, used for spotlight searching 57 | /// 58 | /// 59 | /// CMS 60 | /// 61 | public override string Area => "CMS/pages"; 62 | 63 | /// 64 | /// Gets the CMS page category. 65 | /// 66 | public override string Category => LocalizationService.GetString("/vulcan/searchprovider/pages/name"); 67 | 68 | /// 69 | /// Gets the name of the localization page type. 70 | /// 71 | protected override string ToolTipContentTypeNameResourceKey => "pagetype"; 72 | 73 | /// 74 | /// Gets the page localization path. 75 | /// 76 | protected override string ToolTipResourceKeyBase => "/shell/cms/search/pages/tooltip"; 77 | 78 | /// 79 | /// Gets the icon CSS class for pages. 80 | /// 81 | protected override string IconCssClass(IContent pageData) => "epi-resourceIcon epi-resourceIcon-page"; 82 | } 83 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core.SearchProviders/VulcanBlockSearchProvider.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.SearchProviders 2 | { 3 | using Core; 4 | using EPiServer; 5 | using EPiServer.Core; 6 | using EPiServer.DataAbstraction; 7 | using EPiServer.Framework.Localization; 8 | using EPiServer.Shell; 9 | using EPiServer.Shell.Search; 10 | using EPiServer.Web; 11 | using Implementation; 12 | 13 | /// 14 | /// UI Search provider for blocks 15 | /// 16 | [SearchProvider] 17 | public class VulcanBlockSearchProvider : VulcanSearchProviderBase // using VulcanContentHit due to IContent restriction 18 | { 19 | /// 20 | /// Constructor 21 | /// 22 | public VulcanBlockSearchProvider() 23 | : this( 24 | VulcanHelper.GetService(), 25 | VulcanHelper.GetService(), 26 | VulcanHelper.GetService(), 27 | VulcanHelper.GetService(), 28 | VulcanHelper.GetService(), 29 | VulcanHelper.GetService() 30 | ) 31 | { } 32 | 33 | /// 34 | /// Injectable constructor 35 | /// 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// 41 | /// 42 | public VulcanBlockSearchProvider( 43 | IVulcanHandler vulcanHandler, 44 | LocalizationService localizationService, 45 | ISiteDefinitionResolver siteDefinitionResolver, 46 | IContentRepository contentRepository, 47 | IContentTypeRepository contentTypeRepository, 48 | UIDescriptorRegistry uiDescriptorRegistry 49 | ) 50 | : base(vulcanHandler, contentRepository, contentTypeRepository, localizationService, uiDescriptorRegistry, siteDefinitionResolver) 51 | { 52 | 53 | } 54 | 55 | /// 56 | /// Area that the provider maps to, used for spotlight searching 57 | /// 58 | /// 59 | /// CMS 60 | /// 61 | public override string Area => "CMS/blocks"; 62 | 63 | /// 64 | /// Gets the CMS page category. 65 | /// 66 | public override string Category => LocalizationService.GetString("/vulcan/searchprovider/blocks/name"); 67 | 68 | /// 69 | /// Gets the name of the localization page type. 70 | /// 71 | protected override string ToolTipContentTypeNameResourceKey => "blocktype"; 72 | 73 | /// 74 | /// Gets the page localization path. 75 | /// 76 | protected override string ToolTipResourceKeyBase => "/shell/cms/search/blocks/tooltip"; 77 | 78 | /// 79 | /// Gets the icon CSS class for pages. 80 | /// 81 | protected override string IconCssClass(IContent pageData) => "epi-resourceIcon epi-resourceIcon-block"; 82 | } 83 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core.SearchProviders/VulcanMediaSearchProvider.cs: -------------------------------------------------------------------------------- 1 | using EPiServer; 2 | using EPiServer.Core; 3 | using EPiServer.DataAbstraction; 4 | using EPiServer.Framework.Localization; 5 | using EPiServer.Shell; 6 | using EPiServer.Shell.Search; 7 | using EPiServer.Web; 8 | using TcbInternetSolutions.Vulcan.Core.Extensions; 9 | using TcbInternetSolutions.Vulcan.Core.Implementation; 10 | 11 | namespace TcbInternetSolutions.Vulcan.Core.SearchProviders 12 | { 13 | /// 14 | /// UI Search provider for mediadata 15 | /// 16 | [SearchProvider] 17 | public class VulcanMediaSearchProvider : VulcanSearchProviderBase 18 | { 19 | /// 20 | /// Constructor 21 | /// 22 | public VulcanMediaSearchProvider() 23 | : this( 24 | VulcanHelper.GetService(), 25 | VulcanHelper.GetService(), 26 | VulcanHelper.GetService(), 27 | VulcanHelper.GetService(), 28 | VulcanHelper.GetService(), 29 | VulcanHelper.GetService() 30 | ) 31 | { } 32 | 33 | /// 34 | /// Injected contructor 35 | /// 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// 41 | /// 42 | public VulcanMediaSearchProvider( 43 | IVulcanHandler vulcanHandler, 44 | LocalizationService localizationService, 45 | ISiteDefinitionResolver siteDefinitionResolver, 46 | IContentRepository contentRepository, 47 | IContentTypeRepository contentTypeRepository, 48 | UIDescriptorRegistry uiDescriptorRegistry 49 | ) 50 | : base(vulcanHandler, contentRepository, contentTypeRepository, localizationService, uiDescriptorRegistry, siteDefinitionResolver) 51 | { 52 | 53 | } 54 | 55 | /// 56 | /// 57 | /// 58 | public override bool IncludeInvariant => true; 59 | 60 | /// 61 | /// Area that the provider maps to, used for spotlight searching 62 | /// 63 | /// 64 | /// CMS 65 | /// 66 | public override string Area => "CMS/files"; 67 | 68 | /// 69 | /// Gets the CMS page category. 70 | /// 71 | public override string Category => LocalizationService.GetString("/vulcan/searchprovider/files/name"); 72 | 73 | /// 74 | /// Gets the name of the localization page type. 75 | /// 76 | protected override string ToolTipContentTypeNameResourceKey => ""; 77 | 78 | /// 79 | /// Gets the page localization path. 80 | /// 81 | protected override string ToolTipResourceKeyBase => "/shell/cms/search/files/tooltip"; 82 | 83 | /// 84 | /// Gets the icon CSS class for pages. 85 | /// 86 | protected override string IconCssClass(IContent content) => 87 | "epi-resourceIcon epi-resourceIcon-" + (content as MediaData).SearchFileExtension(); 88 | } 89 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Commerce/VulcanCommerceContentAncestorLoader.cs: -------------------------------------------------------------------------------- 1 | using EPiServer; 2 | using EPiServer.Commerce.Catalog.ContentTypes; 3 | using EPiServer.Commerce.Catalog.Linking; 4 | using EPiServer.Core; 5 | using EPiServer.ServiceLocation; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using TcbInternetSolutions.Vulcan.Core; 10 | 11 | namespace TcbInternetSolutions.Vulcan.Commerce 12 | { 13 | /// 14 | /// Gets ancestors for CMS content 15 | /// 16 | [ServiceConfiguration(typeof(IVulcanContentAncestorLoader), Lifecycle = ServiceInstanceScope.Singleton)] 17 | public class VulcanCommerceContentAncestorLoader : IVulcanContentAncestorLoader 18 | { 19 | private readonly IContentLoader _contentLoader; 20 | private readonly IRelationRepository _relationRepository; 21 | 22 | /// 23 | /// DI Constructor 24 | /// 25 | /// 26 | /// 27 | public VulcanCommerceContentAncestorLoader(IContentLoader contentLoader,IRelationRepository relationRepository) 28 | { 29 | _contentLoader = contentLoader; 30 | _relationRepository = relationRepository; 31 | } 32 | 33 | public IEnumerable GetAncestors(IContent content) 34 | { 35 | var ancestors = new List(); 36 | 37 | if (content is VariationContent variationContent) 38 | { 39 | var productAncestors = variationContent.GetParentProducts()?.ToList(); 40 | 41 | if (productAncestors?.Any() == true) 42 | { 43 | ancestors.AddRange(productAncestors); 44 | ancestors.AddRange(productAncestors.SelectMany(pa => GetAncestorCategoriesIterative(pa, false))); 45 | } 46 | } 47 | 48 | // for these purposes, we assume that products cannot exist inside other products 49 | // variant may also exist directly inside a category 50 | ancestors.AddRange(GetAncestorCategoriesIterative(content.ContentLink, false)); 51 | 52 | return ancestors.Distinct(); 53 | } 54 | 55 | private IEnumerable GetAncestorCategoriesIterative(ContentReference contentLink, bool checkCategoryParent) 56 | { 57 | var ancestors = new List(); 58 | IEnumerable categories; 59 | 60 | try 61 | { 62 | categories = _relationRepository.GetParents(contentLink)?.ToList(); 63 | } 64 | catch (Exception) 65 | { 66 | // probably not a valid category or node type to pull the relations of, so stop the iteration here 67 | return ancestors; 68 | } 69 | 70 | if (categories?.Any() == true) 71 | { 72 | ancestors.AddRange(categories.Select(c => c.Parent)); 73 | ancestors.AddRange(categories.SelectMany(c => GetAncestorCategoriesIterative(c.Parent, true))); 74 | } 75 | 76 | // ReSharper disable once InvertIf 77 | if (checkCategoryParent && _contentLoader.Get(contentLink) is NodeContent thisCat && !ancestors.Contains(thisCat.ParentLink)) 78 | { 79 | ancestors.Add(thisCat.ParentLink); 80 | 81 | ancestors.AddRange(GetAncestorCategoriesIterative(thisCat.ParentLink, true)); 82 | } 83 | 84 | return ancestors; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/TcbInternetSolutions.Vulcan.UI/Scripts/DijitRegistry.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/dom-geometry", 4 | "dijit/_WidgetBase", 5 | "dijit/registry" 6 | ], function (declare, domGeometry, widgetBase, registry) { 7 | var wbPrototype = widgetBase.prototype; 8 | 9 | return declare(null, { 10 | // summary: 11 | // A dgrid extension which will add the grid to the dijit registry, 12 | // so that startup() will be successfully called by dijit layout widgets 13 | // with dgrid children. 14 | 15 | // Defaults normally imposed on _WidgetBase by container widget modules: 16 | minSize: 0, // BorderContainer 17 | maxSize: Infinity, // BorderContainer 18 | layoutPriority: 0, // BorderContainer 19 | showTitle: true, // StackContainer 20 | 21 | buildRendering: function () { 22 | registry.add(this); 23 | this.inherited(arguments); 24 | // Note: for dojo 2.0 may rename widgetId to dojo._scopeName + "_widgetId" 25 | this.domNode.setAttribute("widgetId", this.id); 26 | }, 27 | 28 | startup: function () { 29 | if (this._started) { 30 | return; 31 | } 32 | this.inherited(arguments); 33 | 34 | var widget = this.getParent(); 35 | // If we have a parent layout container widget, it will handle resize, 36 | // so remove the window resize listener added by List. 37 | if (widget && widget.isLayoutContainer) { 38 | this._resizeHandle.remove(); 39 | } 40 | }, 41 | 42 | destroyRecursive: function () { 43 | this.destroy(); 44 | }, 45 | 46 | destroy: function () { 47 | this.inherited(arguments); 48 | registry.remove(this.id); 49 | }, 50 | 51 | getChildren: function () { 52 | // provide hollow implementation for logic which assumes its existence 53 | // (e.g. dijit/form/_FormMixin) 54 | return []; 55 | }, 56 | 57 | getParent: function () { 58 | // summary: 59 | // Analogous to _WidgetBase#getParent, for compatibility with e.g. dijit._KeyNavContainer. 60 | return registry.getEnclosingWidget(this.domNode.parentNode); 61 | }, 62 | 63 | isLeftToRight: function () { 64 | // Implement method expected by Dijit layout widgets 65 | return !this.isRTL; 66 | }, 67 | 68 | placeAt: function (/*String|DomNode|DocumentFragment|dijit/_WidgetBase*/ reference, /*String|Int?*/ position) { 69 | // summary: 70 | // Analogous to dijit/_WidgetBase#placeAt; 71 | // places this widget in the DOM based on domConstruct.place() conventions. 72 | 73 | return wbPrototype.placeAt.call(this, reference, position); 74 | }, 75 | 76 | resize: function (changeSize) { 77 | // Honor changeSize parameter used by layout widgets, and resize grid 78 | if (changeSize) { 79 | domGeometry.setMarginBox(this.domNode, changeSize); 80 | } 81 | 82 | this.inherited(arguments); 83 | }, 84 | 85 | _set: function (prop, value) { 86 | // summary: 87 | // Simple analogue of _WidgetBase#_set for compatibility with some 88 | // Dijit layout widgets which assume its existence. 89 | this[prop] = value; 90 | }, 91 | 92 | watch: function () { 93 | // summary: 94 | // dgrid doesn't support watch; this is a no-op for compatibility with 95 | // some Dijit layout widgets which assume its existence. 96 | } 97 | }); 98 | }); -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Extensions/ContentAreaExtensions.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using EPiServer.DataAbstraction; 3 | using EPiServer.SpecializedProperties; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using TcbInternetSolutions.Vulcan.Core.Implementation; 8 | 9 | namespace TcbInternetSolutions.Vulcan.Core.Extensions 10 | { 11 | /// 12 | /// ContentArea extensions 13 | /// 14 | public static class ContentAreaExtensions 15 | { 16 | /// 17 | /// Converts contentarea to string for indexing 18 | /// 19 | /// 20 | /// 21 | /// 22 | public static string GetContentAreaContents(this ContentArea contentArea, IContentTypeRepository contentTypeRepository = null) 23 | { 24 | if (contentArea == null) { return string.Empty; } 25 | 26 | var stringBuilder = new StringBuilder(); 27 | 28 | foreach (var contentAreaItem in contentArea.Items) 29 | { 30 | var blockData = contentAreaItem.GetContent(); 31 | var props = GetSearchablePropertyValues(blockData, blockData.ContentTypeID, contentTypeRepository); 32 | stringBuilder.AppendFormat(" {0}", string.Join(" ", props)); 33 | } 34 | 35 | return stringBuilder.ToString(); 36 | } 37 | 38 | /// 39 | /// Gets searchable property values for content 40 | /// 41 | /// 42 | /// 43 | /// 44 | /// 45 | public static IEnumerable GetSearchablePropertyValues(IContentData contentData, ContentType contentType, IContentTypeRepository contentTypeRepository) 46 | { 47 | if (contentType == null) 48 | { 49 | yield break; 50 | } 51 | 52 | foreach (var current in from d in contentType.PropertyDefinitions 53 | where d.Searchable || typeof(IPropertyBlock).IsAssignableFrom(d.Type.DefinitionType) 54 | select d) 55 | { 56 | var propertyData = contentData.Property[current.Name]; 57 | 58 | if (propertyData is IPropertyBlock propertyBlock) 59 | { 60 | foreach (var current2 in GetSearchablePropertyValues(propertyBlock.Block, propertyBlock.BlockPropertyDefinitionTypeID, contentTypeRepository)) 61 | { 62 | yield return current2; 63 | } 64 | } 65 | else 66 | { 67 | yield return propertyData.ToWebString(); 68 | } 69 | } 70 | } 71 | 72 | /// 73 | /// Gets searchable propety values for content and type Id 74 | /// 75 | /// 76 | /// 77 | /// 78 | /// 79 | public static IEnumerable GetSearchablePropertyValues(IContentData contentData, int contentTypeId, IContentTypeRepository contentTypeRepository) => 80 | GetSearchablePropertyValues(contentData, ResolveContentTypeRepository(contentTypeRepository).Load(contentTypeId), contentTypeRepository); 81 | 82 | private static IContentTypeRepository ResolveContentTypeRepository(IContentTypeRepository contentTypeRepository) 83 | { 84 | return contentTypeRepository ?? VulcanHelper.GetService(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VULCAN 2 | ------ 3 | 4 | [![Build status](https://ci.appveyor.com/api/projects/status/4266xwr9m0caeb4t?svg=true)](https://ci.appveyor.com/project/dan-matthews/vulcan) 5 | 6 | Firstly, what is it not; It’s NOT Episerver Find and it’s NOT supported – not by Episerver, by me or anyone else. If you want a proper, enterprise-level, supported product with fabulous UI and integration, go dig a little in your pockets and get Find. 7 | 8 | ## What is Vulcan? 9 | It is a small, lightweight wrapper around Elasticsearch’s NEST client that provides helpers and tools to index and search for CMS and Commerce content. It’s simple and as it’s Open Source, you can do what you like with it when it comes to extending and customizing it. You can even host your own Elasticsearch instance, so it could be very cost effective! 10 | 11 | ## About Versioning 12 | 13 | Starting with 4.x versions, anytime a dependency has a breaking change, such as Episerver 12 or Elasticsearch 6, Vulcan will also update all package major versions to make it simpler for developer consumption. There may also still be other breaking changes introduced as well, but all will Vulcan packages will increment to same major version for each occurrence. 14 | 15 | ## Breaking Changes 16 | 17 | Now supports Elasticsearch 5.x! 18 | 19 | Much of the project has been rewritten to take advantage of Episerver's dependency injection system. In doing so, many implementations no longer have empty constructors. Also, implementations for interfaces such as **IVulcanIndexer** and **IVulcanIndexingModifier**, are no longer discovered, they must be manually registered during Episerver initialization. 20 | 21 | To Register **IVulcanIndexer** implementations, they must be done in an **IConfigurableModule** as shown below: 22 | 23 | ```cs 24 | [ModuleDependency(typeof(ServiceContainerInitialization))] 25 | public class RegisterImplementations : IConfigurableModule, IInitializableModule 26 | { 27 | void IConfigurableModule.ConfigureContainer(ServiceConfigurationContext context) 28 | { 29 | // hack: using manual registration as scheduled job doesn't inject otherwise 30 | context.Services.AddSingleton(); 31 | } 32 | 33 | void IInitializableModule.Initialize(InitializationEngine context) { } 34 | 35 | void IInitializableModule.Uninitialize(InitializationEngine context) { } 36 | } 37 | ``` 38 | 39 | All other interfaces may be registered using the **ServiceConfigurationAttribute** as noted below 40 | 41 | ```cs 42 | [ServiceConfiguration(typeof(IVulcanHandler), Lifecycle = ServiceInstanceScope.Singleton)] 43 | ``` 44 | 45 | Another big change is on **IVulcanIndexingModifier** implementations. The interface has changed from: 46 | 47 | ```cs 48 | void ProcessContent(IContent content, Stream writableStream); 49 | ``` 50 | 51 | to: 52 | 53 | ```cs 54 | void ProcessContent(IVulcanIndexingModifierArgs modifierArgs); 55 | ``` 56 | 57 | Where **IVulcanIndexingModifierArgs** provides the IContent.Content instance and IDictionary.AdditionalItems for adding more fields to be indexed. The AdditionalItems property is merged with the main IContent serialization. Below is an example of adding a new field to be indexed: 58 | 59 | ```cs 60 | void ProcessContent(IVulcanIndexingModifierArgs args) 61 | { 62 | // index ancestors 63 | var ancestors = new List(); 64 | 65 | // constructor injected service 66 | if (_VulcanContentAncestorLoaders?.Any() == true) 67 | { 68 | foreach (var ancestorLoader in _VulcanContentAncestorLoaders) 69 | { 70 | IEnumerable ancestorsFound = ancestorLoader.GetAncestors(args.Content); 71 | 72 | if (ancestorsFound?.Any() == true) 73 | { 74 | ancestors.AddRange(ancestorsFound); 75 | } 76 | } 77 | } 78 | 79 | args.AdditionalItems[VulcanFieldConstants.Ancestors] = ancestors.Select(x => x.ToReferenceWithoutVersion()).Distinct(); 80 | } 81 | ``` -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanCmsIndexingModifier.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 2 | { 3 | using EPiServer.Core; 4 | using EPiServer.DataAbstraction; 5 | using EPiServer.Security; 6 | using EPiServer.ServiceLocation; 7 | using Extensions; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | 12 | /// 13 | /// Default CMS content indexing modifier 14 | /// 15 | [ServiceConfiguration(typeof(IVulcanIndexingModifier), Lifecycle = ServiceInstanceScope.Singleton)] 16 | public class VulcanCmsIndexingModifier : IVulcanIndexingModifier 17 | { 18 | private readonly IContentSecurityRepository _contentSecurityDescriptor; 19 | private readonly IEnumerable _vulcanContentAncestorLoaders; 20 | 21 | /// 22 | /// DI Constructor 23 | /// 24 | /// 25 | /// 26 | public VulcanCmsIndexingModifier(IContentSecurityRepository contentSecurityRepository, IEnumerable vulcanContentAncestorLoader) 27 | { 28 | _contentSecurityDescriptor = contentSecurityRepository; 29 | _vulcanContentAncestorLoaders = vulcanContentAncestorLoader; 30 | } 31 | 32 | /// 33 | /// Writes additional IContent information to stream 34 | /// 35 | /// 36 | public virtual void ProcessContent(IVulcanIndexingModifierArgs args) 37 | { 38 | // index ancestors 39 | var ancestors = new List(); 40 | 41 | if (_vulcanContentAncestorLoaders?.Any() == true) 42 | { 43 | foreach (var ancestorLoader in _vulcanContentAncestorLoaders) 44 | { 45 | var ancestorsFound = ancestorLoader.GetAncestors(args.Content)?.ToList(); 46 | 47 | if (ancestorsFound?.Any() == true) 48 | { 49 | ancestors.AddRange(ancestorsFound); 50 | } 51 | } 52 | } 53 | 54 | args.AdditionalItems[VulcanFieldConstants.Ancestors] = ancestors.Select(x => x.ToReferenceWithoutVersion()).Distinct(); 55 | 56 | // index read permission 57 | var permissions = _contentSecurityDescriptor.Get(args.Content.ContentLink); 58 | 59 | if (permissions != null) // will be null for commerce products, compatibility handled in commerce modifier 60 | { 61 | args.AdditionalItems[VulcanFieldConstants.ReadPermission] = permissions.Entries. 62 | Where(x => 63 | x.Access.HasFlag(AccessLevel.Read) || 64 | x.Access.HasFlag(AccessLevel.Administer) || 65 | x.Access.HasFlag(AccessLevel.FullAccess)).Select(x => x.Name); 66 | 67 | } 68 | 69 | // index VulcanSearchableAttribute 70 | var contents = new List(); 71 | var properties = args.Content.GetType().GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(VulcanSearchableAttribute))); 72 | 73 | foreach (var p in properties) 74 | { 75 | var value = p.GetValue(args.Content); 76 | 77 | // Property to string conversions 78 | if (p.PropertyType == typeof(ContentArea)) 79 | { 80 | value = (value as ContentArea).GetContentAreaContents(); 81 | } 82 | 83 | var v = value?.ToString(); 84 | 85 | if (!string.IsNullOrWhiteSpace(v)) 86 | { 87 | contents.Add(v); 88 | } 89 | } 90 | 91 | args.AdditionalItems[VulcanFieldConstants.CustomContents] = string.Join(" ", contents); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | 214 | GeneratedAssemblyInfo.cs -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/IVulcanHandler.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | 6 | namespace TcbInternetSolutions.Vulcan.Core 7 | { 8 | /// 9 | /// Used to help modifiers handle deletes. 10 | /// 11 | public delegate void IndexDeleteHandler(IEnumerable deletedIndexes); 12 | 13 | /// 14 | /// Vulcan handler contract 15 | /// 16 | public interface IVulcanHandler 17 | { 18 | /// 19 | /// Delete indices handler 20 | /// 21 | IndexDeleteHandler DeletedIndices { get; set; } 22 | 23 | /// 24 | /// Index name 25 | /// 26 | string Index { get; } 27 | 28 | /// 29 | /// Index modifier list 30 | /// 31 | IEnumerable IndexingModifers { get; } 32 | 33 | /// 34 | /// Adds instruction for indexing rules 35 | /// 36 | /// 37 | /// 38 | void AddConditionalContentIndexInstruction(Func instruction) where T : IContent; 39 | 40 | /// 41 | /// Determines if content can be indexed 42 | /// 43 | /// 44 | /// 45 | bool AllowContentIndexing(IContent objectToIndex); 46 | 47 | /// 48 | /// Delete content by language 49 | /// 50 | /// 51 | /// 52 | void DeleteContentByLanguage(IContent content, string alias = null); 53 | 54 | /// 55 | /// Delete content for all languages 56 | /// 57 | /// 58 | /// 59 | /// 60 | void DeleteContentEveryLanguage(ContentReference contentLink, string typeName, string alias = null); 61 | 62 | /// 63 | /// Delete index 64 | /// 65 | void DeleteIndex(string alias = null); 66 | 67 | /// 68 | /// Get vulcan client by culture 69 | /// 70 | /// 71 | /// 72 | /// 73 | IVulcanClient GetClient(CultureInfo language = null, string alias = null); 74 | 75 | /// 76 | /// Switch to new indexAlias 77 | /// 78 | /// 79 | /// 80 | /// 81 | void SwitchAlias(CultureInfo language, string oldAlias, string newAlias); 82 | 83 | /// 84 | /// Switch all cultures to new indexAlias 85 | /// 86 | /// 87 | /// 88 | void SwitchAliasAllCultures(string oldAlias, string newAlias); 89 | 90 | /// 91 | /// Get all vulcan clients 92 | /// 93 | /// 94 | IVulcanClient[] GetClients(string alias = null); 95 | 96 | /// 97 | /// Index content by language 98 | /// 99 | /// 100 | /// 101 | void IndexContentByLanguage(IContent content, string alias = null); 102 | 103 | /// 104 | /// Index content for all languages 105 | /// 106 | /// 107 | /// 108 | void IndexContentEveryLanguage(ContentReference contentLink, string alias = null); 109 | 110 | /// 111 | /// Index content for all languages 112 | /// 113 | /// 114 | /// 115 | void IndexContentEveryLanguage(IContent content, string alias = null); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.Core/Implementation/VulcanClientConnectionSettings.cs: -------------------------------------------------------------------------------- 1 | using Elasticsearch.Net; 2 | using EPiServer.ServiceLocation; 3 | using Nest; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace TcbInternetSolutions.Vulcan.Core.Implementation 8 | { 9 | /// 10 | /// Default Vulcan Client Connection settings, using a single connection pool and app setting values 11 | /// 12 | [ServiceConfiguration(typeof(IVulcanClientConnectionSettings), Lifecycle = ServiceInstanceScope.Singleton)] 13 | public class VulcanClientConnectionSettings : IVulcanClientConnectionSettings 14 | { 15 | private readonly IVulcanConnectionPoolFactory _vulcanConnectionpoolFactory; 16 | private readonly IEnumerable _vulcanIndexerModifers; 17 | private readonly IVulcanModfiySerializerSettings _vulcanModfiySerializerSettings; 18 | private readonly VulcanApplicationSettings _vulcanApplicationSettings; 19 | 20 | /// 21 | /// Injected constructor 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | public VulcanClientConnectionSettings 28 | ( 29 | IVulcanConnectionPoolFactory connectionpoolFactory, 30 | IEnumerable vulcanIndexingModifiers, 31 | IVulcanModfiySerializerSettings vulcanModfiySerializerSettings, 32 | VulcanApplicationSettings vulcanApplicationSettings 33 | ) 34 | { 35 | _vulcanConnectionpoolFactory = connectionpoolFactory; 36 | _vulcanIndexerModifers = vulcanIndexingModifiers; 37 | _vulcanModfiySerializerSettings = vulcanModfiySerializerSettings; 38 | _vulcanApplicationSettings = vulcanApplicationSettings; 39 | } 40 | 41 | /// 42 | /// Gets common settings from AppSetting keys 'Url', 'IndexNamePrefix', 'Username' (optional), 'Password' (optional), 'VulcanEnableHttpCompression' (optional true/false) 43 | /// 44 | public virtual ConnectionSettings ConnectionSettings => CommonSettings(); 45 | 46 | /// 47 | /// Value of AppSetting 'IndexNamePrefix' 48 | /// 49 | public virtual string Index => _vulcanApplicationSettings.IndexNamePrefix; 50 | 51 | /// 52 | /// Common connection settings 53 | /// 54 | /// 55 | protected virtual ConnectionSettings CommonSettings() 56 | { 57 | var url = _vulcanApplicationSettings.Url; 58 | var index = _vulcanApplicationSettings.IndexNamePrefix; 59 | 60 | if (string.IsNullOrWhiteSpace(url) || url == "SET THIS") 61 | { 62 | throw new Exception("You need to specify the Vulcan Url in AppSettings"); 63 | } 64 | 65 | if (string.IsNullOrWhiteSpace(index) || index == "SET THIS") 66 | { 67 | throw new Exception("You need to specify the Vulcan Index in AppSettings"); 68 | } 69 | 70 | var connectionPool = _vulcanConnectionpoolFactory.CreateConnectionPool(url); 71 | var settings = new ConnectionSettings(connectionPool, CreateJsonSerializer); 72 | var username = _vulcanApplicationSettings.Username; 73 | var password = _vulcanApplicationSettings.Password; 74 | 75 | if (!string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password)) 76 | { 77 | settings.BasicAuthentication(username, password); 78 | } 79 | 80 | // Enable bytes to be retrieved in debug mode 81 | settings.DisableDirectStreaming(_vulcanApplicationSettings.IsDebugMode); 82 | 83 | // Enable compression 84 | settings.EnableHttpCompression(_vulcanApplicationSettings.EnableHttpCompression); 85 | 86 | return settings; 87 | } 88 | 89 | /// 90 | /// Creates default serializer for Vulcan, only override in advanced cases 91 | /// 92 | /// 93 | /// 94 | protected virtual IElasticsearchSerializer CreateJsonSerializer(ConnectionSettings s) 95 | { 96 | return new VulcanCustomJsonSerializer(s, _vulcanIndexerModifers, _vulcanModfiySerializerSettings.Modifier); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/VulcanAttachmentIndexModifier.cs: -------------------------------------------------------------------------------- 1 | namespace TcbInternetSolutions.Vulcan.AttachmentIndexer 2 | { 3 | using EPiServer.Core; 4 | using EPiServer.ServiceLocation; 5 | using System; 6 | using Core; 7 | using Core.Implementation; 8 | using static Core.VulcanFieldConstants; 9 | 10 | /// 11 | /// Adds attachment content to serialized data 12 | /// 13 | [ServiceConfiguration(typeof(IVulcanIndexingModifier), Lifecycle = ServiceInstanceScope.Singleton)] 14 | public class VulcanAttachmentIndexModifier : IVulcanIndexingModifier 15 | { 16 | private readonly IVulcanMediaReader _mediaReader; 17 | private readonly IVulcanBytesToStringConverter _byteConvertor; 18 | private readonly IVulcanPipelineSelector _vulcanPipelineSelector; 19 | 20 | // store the attachment pipeline for NEST 2 since its a singleton and no need to get it for every asset 21 | private IVulcanPipeline _attachmentPipeline; 22 | 23 | private Type _converterType; 24 | 25 | /// 26 | /// DI Constructor 27 | /// 28 | /// 29 | /// 30 | /// 31 | public VulcanAttachmentIndexModifier 32 | ( 33 | IVulcanMediaReader vulcanMediaReader, 34 | IVulcanBytesToStringConverter vulcanBytesToStringConverter, 35 | IVulcanPipelineSelector vulcanPipelineSelector 36 | ) 37 | { 38 | _mediaReader = vulcanMediaReader; 39 | _byteConvertor = vulcanBytesToStringConverter; 40 | _vulcanPipelineSelector = vulcanPipelineSelector; 41 | } 42 | 43 | /// 44 | /// Adds attachment content to serialized data 45 | /// 46 | /// 47 | public void ProcessContent(IVulcanIndexingModifierArgs args) 48 | { 49 | if (!IsMediaReadable(args, out var isPipeline, out var media)) return; 50 | var mediaBytes = _mediaReader.ReadToEnd(media); 51 | var base64Contents = Convert.ToBase64String(mediaBytes); 52 | var mimeType = media.MimeType; 53 | 54 | var stringContents = _byteConvertor.ConvertToString(mediaBytes, mimeType); 55 | 56 | if (!string.IsNullOrWhiteSpace(stringContents)) 57 | { 58 | args.AdditionalItems[MediaStringContents] = stringContents; 59 | } 60 | 61 | if (!isPipeline) return; 62 | 63 | #if NEST2 64 | var mediaFields = new Dictionary 65 | { 66 | ["_name"] = media.Name, 67 | ["_indexed_chars"] = -1,// indexes entire document instead of first 100000 chars 68 | ["_content_type"] = mimeType, 69 | ["_content_length"] = mediaBytes.LongLength, 70 | ["_content"] = base64Contents 71 | }; 72 | 73 | args.AdditionalItems[MediaContents] = mediaFields; 74 | #elif NEST5 75 | // 5x: only send base64 content if pipeline is enabled 76 | args.AdditionalItems[MediaContents] = base64Contents; 77 | #endif 78 | } 79 | 80 | private bool IsMediaReadable(IVulcanIndexingModifierArgs args, out bool isPipeline, out MediaData media) 81 | { 82 | media = args.Content as MediaData; 83 | isPipeline = false; 84 | 85 | if (media == null) return false; 86 | 87 | if (_attachmentPipeline == null) 88 | { 89 | _attachmentPipeline = _vulcanPipelineSelector.GetPipelineById(Implementation.VulcanAttachmentPipelineInstaller.PipelineId); 90 | } 91 | 92 | #if NEST2 93 | // for 2x, have to evaluate pipeline here 94 | if (_attachmentPipeline?.IsMatch(args.Content) == true) 95 | { 96 | return isPipeline = true; 97 | } 98 | #endif 99 | 100 | if (args.PipelineId == Implementation.VulcanAttachmentPipelineInstaller.PipelineId) 101 | { 102 | return isPipeline = true; 103 | } 104 | 105 | if (_converterType == null) 106 | { 107 | _converterType = _byteConvertor.GetType(); 108 | } 109 | 110 | // default converter does nothing so don't read it 111 | return _converterType != typeof(DefaultVulcanBytesToStringConverter); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.UI/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Elasticsearch.Net; 2 | using EPiServer.Shell.Navigation; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Web.Mvc; 8 | using TcbInternetSolutions.Vulcan.Core; 9 | using TcbInternetSolutions.Vulcan.UI.Models.ViewModels; 10 | using TcbInternetSolutions.Vulcan.UI.Support; 11 | 12 | namespace TcbInternetSolutions.Vulcan.UI.Controllers 13 | { 14 | /// 15 | /// UI Vulcan Controller 16 | /// 17 | [Authorize(Roles = "Administrators,CmsAdmins,WebAdmins,VulcanAdmins")] 18 | public class HomeController : Base.BaseController 19 | { 20 | private readonly IEnumerable _vulcanIndexers; 21 | private readonly IEnumerable _vulcanIndexModifiers; 22 | 23 | /// 24 | /// DI Constructor 25 | /// 26 | /// 27 | /// 28 | /// 29 | public HomeController 30 | ( 31 | IVulcanHandler vulcanHandler, 32 | IEnumerable vulcanIndexers, 33 | IEnumerable vulcanIndexModifiers 34 | ) : base(vulcanHandler) 35 | { 36 | _vulcanIndexers = vulcanIndexers; 37 | _vulcanIndexModifiers = vulcanIndexModifiers; 38 | } 39 | 40 | /// 41 | /// Main UI View 42 | /// 43 | /// 44 | [MenuItem("/global/vulcan", Text = "Vulcan")] 45 | [HttpGet] 46 | public ActionResult Index() 47 | { 48 | var clients = VulcanHandler.GetClients()?.OrderBy(x => x.Language.EnglishName).ToList(); 49 | 50 | var viewModel = new HomeViewModel 51 | { 52 | VulcanClients = clients, 53 | VulcanHandler = VulcanHandler, 54 | PocoIndexers = _vulcanIndexers.OfType(), 55 | VulcanIndexModifiers = _vulcanIndexModifiers, 56 | ProtectedUiPath = EPiServer.Shell.Paths.ProtectedRootPath 57 | }; 58 | 59 | // ReSharper disable once InvertIf 60 | if (clients?.Any() == true) 61 | { 62 | var healthResponse = clients[0].CatIndices(); 63 | 64 | viewModel.IndexHealthDescriptor.AddRange(healthResponse.Records.Where(r => r.Index.StartsWith(VulcanHandler.Index))); 65 | 66 | // doc types count 67 | var typeCounts = new Dictionary>>(); 68 | 69 | foreach (var client in clients) 70 | { 71 | var uiDisplayName = client.Language.Equals(CultureInfo.InvariantCulture) ? 72 | "non-specific" : 73 | $"{client.Language.EnglishName} ({client.Language.Name})"; 74 | 75 | //search can error in some situations 76 | Nest.BucketAggregate typeCount; 77 | 78 | try 79 | { 80 | typeCount = client.Search(m => m.AllTypes() 81 | .SearchType(SearchType.DfsQueryThenFetch). // possible 5x to 2x difference 82 | Aggregations(aggs => aggs.Terms("typeCount", t => t.Field("_type")))) 83 | .Aggregations["typeCount"] as Nest.BucketAggregate; 84 | } 85 | catch 86 | { 87 | typeCount = null; 88 | } 89 | 90 | var docCounts = new List(); 91 | long total = 0; 92 | #if NEST2 93 | var buckets = typeCount?.Items.OfType() ?? new List(); 94 | 95 | foreach (var type in buckets) 96 | { 97 | total += type.DocCount ?? 0; 98 | docCounts.Add($"{type.Key}({type.DocCount})"); 99 | } 100 | #elif NEST5 101 | var buckets = typeCount?.Items.OfType>() ?? new List>(); 102 | 103 | foreach (var type in buckets) 104 | { 105 | total += type.DocCount ?? 0; 106 | docCounts.Add($"{type.Key}({type.DocCount})"); 107 | } 108 | #endif 109 | 110 | typeCounts[client.IndexName] = Tuple.Create(total, uiDisplayName, docCounts); 111 | } 112 | 113 | viewModel.ClientViewInfo = typeCounts; 114 | } 115 | 116 | return View(Helper.ResolveView("Home/Index.cshtml"), viewModel); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /src/TcbInternetSolutions.Vulcan.AttachmentIndexer/Implementation/VulcanAttachmentPipelineInstaller.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | using System; 3 | using System.Globalization; 4 | using System.Linq; 5 | using TcbInternetSolutions.Vulcan.Core; 6 | using static TcbInternetSolutions.Vulcan.Core.VulcanFieldConstants; 7 | 8 | namespace TcbInternetSolutions.Vulcan.AttachmentIndexer.Implementation 9 | { 10 | /// 11 | /// Installs Attachments pipeline 12 | /// 13 | [ServiceConfiguration(typeof(IVulcanPipelineInstaller), Lifecycle = ServiceInstanceScope.Singleton)] 14 | public class VulcanAttachmentPipelineInstaller : IVulcanPipelineInstaller 15 | { 16 | /// 17 | /// Advanced option for forcing attachments for a type in Nest 2x where new media types were added later 18 | /// 19 | public static Func MapAttachment; 20 | 21 | /// 22 | /// Attachment Pipeline ID 23 | /// 24 | public static readonly string PipelineId = "vulcan-attachment"; 25 | 26 | // 5.x support uses https://www.elastic.co/guide/en/elasticsearch/plugins/5.2/ingest-attachment.html 27 | // not which 2.x uses https://www.elastic.co/guide/en/elasticsearch/plugins/5.2/mapper-attachments.html 28 | private const string PluginName = 29 | #if NEST2 30 | "mapper-attachments" // older 2.x 31 | #elif NEST5 32 | "ingest-attachment" 33 | #endif 34 | ; 35 | 36 | private readonly IVulcanAttachmentIndexerSettings _vulcanAttachmentIndexerSettings; 37 | 38 | /// 39 | /// DI Constructor 40 | /// 41 | /// 42 | public VulcanAttachmentPipelineInstaller(IVulcanAttachmentIndexerSettings vulcanAttachmentIndexerSettings) 43 | { 44 | _vulcanAttachmentIndexerSettings = vulcanAttachmentIndexerSettings; 45 | } 46 | 47 | string IVulcanPipelineInstaller.Id => PipelineId; 48 | 49 | void IVulcanPipelineInstaller.Install(IVulcanClient client) 50 | { 51 | if (!_vulcanAttachmentIndexerSettings.EnableAttachmentPlugins || !client.Language.Equals(CultureInfo.InvariantCulture)) return; 52 | var info = client.NodesInfo(); 53 | 54 | if (info?.Nodes?.Any(x => x.Value?.Plugins?.Any(y => string.Compare(y.Name, PluginName, StringComparison.OrdinalIgnoreCase) == 0) == true) != true) 55 | { 56 | throw new Exception($"No attachment plugin found, be sure to install the '{PluginName}' plugin on your Elastic Search Server!"); 57 | } 58 | 59 | #if NEST2 60 | // v2, to do, get all MediaData types that are allowed and loop them 61 | var mediaDataTypes = Core.Extensions.TypeExtensions.GetSearchTypesFor(t => t.IsAbstract == false); 62 | 63 | foreach (var mediaType in mediaDataTypes) 64 | { 65 | var descriptors = mediaType.GetCustomAttributes(false).OfType(); 66 | var extensionStrings = string.Join(",", descriptors.Select(x => x.ExtensionString ?? "")); 67 | var extensions = extensionStrings.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 68 | var manualCheck = MapAttachment?.Invoke(mediaType) ?? false; 69 | 70 | // only map ones we allow 71 | if (!extensions.Intersect(_vulcanAttachmentIndexerSettings.SupportedFileExtensions).Any() && !manualCheck) continue; 72 | 73 | var response = client.Map(m => m. 74 | Index(client.IndexName). // was _all 75 | Type(mediaType.FullName). 76 | Properties(props => props. 77 | Attachment(s => s.Name(MediaContents) 78 | .FileField(ff => ff.Name("content").Store().TermVector(Nest.TermVectorOption.WithPositionsOffsets)) 79 | )) 80 | ); 81 | 82 | if (!response.IsValid) 83 | { 84 | throw new Exception(response.DebugInformation); 85 | } 86 | } 87 | #elif NEST5 88 | // v5, use pipeline 89 | var response = client.PutPipeline(PipelineId, p => p 90 | .Description("Document attachment pipeline") 91 | .Processors(pr => pr 92 | .Attachment(a => a 93 | .Field(MediaContents) 94 | .TargetField(MediaContents) 95 | .IndexedCharacters(-1) 96 | ) 97 | ) 98 | ); 99 | 100 | if (!response.IsValid) 101 | { 102 | throw new Exception(response.DebugInformation); 103 | } 104 | #endif 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /TcbInternetSolutions.Vulcan.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2037 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6AADB6AF-EDDF-4E41-A87E-C97B8156AF6E}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitignore = .gitignore 9 | appveyor.yml = appveyor.yml 10 | .nuget\NuGet.Config = .nuget\NuGet.Config 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcbInternetSolutions.Vulcan.Core", "src\TcbInternetSolutions.Vulcan.Core\TcbInternetSolutions.Vulcan.Core.csproj", "{193748AD-DB0C-424B-931D-CC69F2CF0F1A}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcbInternetSolutions.Vulcan.AttachmentIndexer", "src\TcbInternetSolutions.Vulcan.AttachmentIndexer\TcbInternetSolutions.Vulcan.AttachmentIndexer.csproj", "{3C98395B-5E08-44D0-AC3A-0A6440C1C802}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcbInternetSolutions.Vulcan.Core.SearchProviders", "src\TcbInternetSolutions.Vulcan.Core.SearchProviders\TcbInternetSolutions.Vulcan.Core.SearchProviders.csproj", "{D86F5B46-620F-4E37-AF82-767B9244C577}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcbInternetSolutions.Vulcan.UI", "src\TcbInternetSolutions.Vulcan.UI\TcbInternetSolutions.Vulcan.UI.csproj", "{99A27C94-DF6B-46CE-92AD-D35E2E6A0BC6}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcbInternetSolutions.Vulcan.Commerce", "src\TcbInternetSolutions.Vulcan.Commerce\TcbInternetSolutions.Vulcan.Commerce.csproj", "{D35DF614-19C1-4CC8-9C46-982916B94C96}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcbInternetSolutions.Vulcan.Commerce.SearchProviders", "src\TcbInternetSolutions.Vulcan.Commerce.SearchProviders\TcbInternetSolutions.Vulcan.Commerce.SearchProviders.csproj", "{864615C1-2F0F-4639-805A-095A0C4CDECF}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed", "src\TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed\TcbInternetSolutions.Vulcan.Commerce.GoogleProductFeed.csproj", "{C39A098C-D47C-45C6-AC74-3FEF10597866}" 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "targets", "targets", "{09CE4433-208F-49F8-A60A-6A609C7485DE}" 29 | ProjectSection(SolutionItems) = preProject 30 | src\targets\AddReadMeMarkdown.targets = src\targets\AddReadMeMarkdown.targets 31 | src\targets\AddReleaseNotes.targets = src\targets\AddReleaseNotes.targets 32 | src\targets\CommonBuild.props = src\targets\CommonBuild.props 33 | src\targets\ZipEpiserverModule.targets = src\targets\ZipEpiserverModule.targets 34 | EndProjectSection 35 | EndProject 36 | Global 37 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 38 | Debug|Any CPU = Debug|Any CPU 39 | Release|Any CPU = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 42 | {193748AD-DB0C-424B-931D-CC69F2CF0F1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {193748AD-DB0C-424B-931D-CC69F2CF0F1A}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {193748AD-DB0C-424B-931D-CC69F2CF0F1A}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {193748AD-DB0C-424B-931D-CC69F2CF0F1A}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {3C98395B-5E08-44D0-AC3A-0A6440C1C802}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {3C98395B-5E08-44D0-AC3A-0A6440C1C802}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {3C98395B-5E08-44D0-AC3A-0A6440C1C802}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {3C98395B-5E08-44D0-AC3A-0A6440C1C802}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {D86F5B46-620F-4E37-AF82-767B9244C577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {D86F5B46-620F-4E37-AF82-767B9244C577}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {D86F5B46-620F-4E37-AF82-767B9244C577}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {D86F5B46-620F-4E37-AF82-767B9244C577}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {99A27C94-DF6B-46CE-92AD-D35E2E6A0BC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {99A27C94-DF6B-46CE-92AD-D35E2E6A0BC6}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {99A27C94-DF6B-46CE-92AD-D35E2E6A0BC6}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {99A27C94-DF6B-46CE-92AD-D35E2E6A0BC6}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {D35DF614-19C1-4CC8-9C46-982916B94C96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {D35DF614-19C1-4CC8-9C46-982916B94C96}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {D35DF614-19C1-4CC8-9C46-982916B94C96}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {D35DF614-19C1-4CC8-9C46-982916B94C96}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {864615C1-2F0F-4639-805A-095A0C4CDECF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {864615C1-2F0F-4639-805A-095A0C4CDECF}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {864615C1-2F0F-4639-805A-095A0C4CDECF}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {864615C1-2F0F-4639-805A-095A0C4CDECF}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {C39A098C-D47C-45C6-AC74-3FEF10597866}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {C39A098C-D47C-45C6-AC74-3FEF10597866}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {C39A098C-D47C-45C6-AC74-3FEF10597866}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {C39A098C-D47C-45C6-AC74-3FEF10597866}.Release|Any CPU.Build.0 = Release|Any CPU 70 | EndGlobalSection 71 | GlobalSection(SolutionProperties) = preSolution 72 | HideSolutionNode = FALSE 73 | EndGlobalSection 74 | GlobalSection(ExtensibilityGlobals) = postSolution 75 | SolutionGuid = {7B846582-AAC0-497A-A35C-BC1E5EE66413} 76 | EndGlobalSection 77 | EndGlobal 78 | --------------------------------------------------------------------------------