├── .gitattributes ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── NuGet.config ├── README.md ├── SmartSolrSchema.sln ├── build ├── PackageNuGet.cmd ├── PackageNuGet.ps1 └── Push NuGet.ps1 ├── src └── SmartSolrSchema │ ├── App_Config │ └── Modules │ │ └── SmartSolrSchema │ │ ├── DateTimeSortable.config │ │ └── PopulateSolrSchema.config │ ├── ComputedFields │ └── GenericDateField.cs │ ├── Pipelines │ └── SolrProvider │ │ └── PopulateSolrSchemaHelper.cs │ └── SmartSolrSchema.csproj └── tools └── NuGet.exe /.gitattributes: -------------------------------------------------------------------------------- 1 | *.cs text=auto diff=csharp 2 | *.html text=auto 3 | *.htm text=auto 4 | *.css text=auto 5 | *.scss text=auto 6 | *.sass text=auto 7 | *.less text=auto 8 | *.js text=auto 9 | *.sql text=auto 10 | 11 | *.csproj text=auto merge=union 12 | *.sln text=auto eol=crlf merge=union 13 | 14 | *.item -text 15 | 16 | *.config linguist-language=XML -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.101 20 | - name: Build 21 | run: dotnet build --configuration Release 22 | - name: Test 23 | run: dotnet test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ######## GENERAL IGNORES ####### 2 | 3 | # ignore logs, OS cache files 4 | *.log 5 | .DS_Store* 6 | ehthumbs.db 7 | Icon? 8 | Thumbs.db 9 | .vs 10 | 11 | ######## GENERAL .NET IGNORES ######## 12 | 13 | # Ignore binaries in the source 14 | src/*/bin 15 | src/*/obj 16 | lib/sitecore/* 17 | !lib/sitecore/readme.txt 18 | src/Dianoga.Tests/Optimizers/Pipelines/DianogaSvg/SVGO 19 | 20 | # Ignore user-scoped solution and project settings 21 | *.user 22 | *.suo 23 | 24 | # NuGet packages 25 | packages/* 26 | !packages/repositories.config 27 | Build/*.nupkg -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Welcome, and thank you for your interest in contributing ! 4 | 5 | There are many ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved. 6 | 7 | ## Providing Feedback 8 | 9 | Your comments and feedback are welcome - you can open an issue, or reach out on [Sitecore Slack](https://sitecore.chat/) 10 | 11 | ## Reporting Issues 12 | 13 | Have you identified a reproducible problem? Have a feature request? We want to hear about it! Here's how you can make reporting your issue as effective as possible. 14 | 15 | ### Look For an Existing Issue 16 | 17 | Before you create a new issue, please do a search in issues to see if the issue or feature request has already been filed. 18 | 19 | If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment: 20 | 21 | * 👍 - upvote 22 | * 👎 - downvote 23 | 24 | If you cannot find an existing issue that describes your bug or feature, create a new issue using the guidelines below. 25 | 26 | ### Writing Good Bug Reports and Feature Requests 27 | 28 | File a single issue per problem and feature request. Do not enumerate multiple bugs or feature requests in the same issue. 29 | 30 | Do not add your issue as a comment to an existing issue unless it's for the identical input. Many issues look similar, but have different causes. 31 | 32 | The more information you can provide, the more likely someone will be successful at reproducing the issue and finding a fix. 33 | 34 | Please include the following with each issue: 35 | 36 | * Version 37 | 38 | * Environment description 39 | 40 | * What configs you have enabled 41 | 42 | * Reproducible steps (1... 2... 3...) that cause the issue 43 | 44 | * What you expected to see, versus what you actually saw 45 | 46 | * Relevant logs 47 | 48 | # Thank You! 49 | 50 | Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute. 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mark Gibbons (Triggerfish) 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. 22 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SmartSolrSchema 2 | 3 | This module enhances the built-in Sitecore Solr Populate Managed Schema functionality by populating not only standard Sitecore dynamic fields but also reads any custom languages that are set up in the Sitecore Master database under `/sitecore/system/languages`. 4 | 5 | ## Why? 6 | 7 | If you add a new language to Sitecore, for example Korean, and start adding items based on that language, when it comes to indexing on Solr you will see issues about missing dynamic fields about the `ko` language. 8 | 9 | Example: 10 | 11 | > org.apache.solr.common.SolrException: ERROR: [doc=sitecore://master/{bfde3d21-3a67-4938-aa90-33da9caf7bf5}?lang=cs-cz&ver=1] unknown field '__display_name_t_cs' 12 | 13 | Usually you would then need to manually patch the Solr schema yourself [like this](https://sitecore.stackexchange.com/a/2042/1278). 14 | 15 | That is a very manual process and makes the DevOps side of things much more tricky. So let's automate it! 16 | 17 | ### Date *_dtm Bugfix 18 | 19 | This module also fixes a Sitecore bug where Solr datetimeCollection has fieldNameFormat="{0}_dtm" but the schema builder format is "*_tdtm" 20 | 21 | ### Date Sorting 22 | 23 | There is a somewhat common issue with sorting by date fields in Solr where it does not always sort correctly. It happens more often if you have a lot of items that you are searching and ordering. 24 | This has been blogged about [here](https://www.sitecorenutsbolts.net/2014/11/06/Sitecore-Sorting-by-Date-with-SOLR/) by Rich Seal, where the fix is to add a date field to the solr managed schema that is `indexed="true" stored="false"`. 25 | I have made this easier for you by automatically adding a `*_tdts` dynamic field with this setting. You can then use it in your project as follows: 26 | 27 | 1. Open the `App_Config/Modules/SmartSolrSchema/DateTimeSortable.config` and uncomment the example computed field. Update the `fieldName` parameter with your field name. 28 | 2. Add as many fields as you need to sort by here. 29 | 3. Build and deploy your solution and do a full index rebuild 30 | 4. You can now sort by `fieldname_tdts` which will return accurate results. 31 | 32 | ## Sitecore Version Support 33 | 34 | * Sitecore 10.1.x and later - Supported - Use the [SmartSolrSchema.SC101](https://www.nuget.org/packages/SmartSolrSchema.SC101) nuget package 35 | * Sitecore 9.1.x to 10.0.x - Supported - Use the [SmartSolrSchema.SC91-100](https://www.nuget.org/packages/SmartSolrSchema.SC91-100) nuget package 36 | * Sitecore 9.x and earlier - Not Supported 37 | 38 | ## How to install 39 | 40 | 1. Install the nuget package. 41 | 2. If you're using PackageReferences then you'll also need to copy in the configs in the package `App_Config/Modules/SmartSolrSchema` 42 | 3. Build your solution and ensure the `SmartSolrSchema.dll` and configs are deployed 43 | 44 | ## How to use 45 | 46 | 1. Log into Sitecore and go to the Control Panel 47 | 2. Open the Populate Solr Managed Schema window 48 | 3. Check the indexes you want to populate and click Populate 49 | 4. Ensure that the process Succeeded. You can also check the logs to see what custom languages have been populated. For example: `SmartSolrSchema: adding custom defined language to schema ko` 50 | 5. Close the dialog and open the Indexing Manager 51 | 6. Rebuild the indexes 52 | 7. You can check the Crawling log file to make sure you're not getting errors about unknown fields. 53 | -------------------------------------------------------------------------------- /SmartSolrSchema.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29613.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FE4508D-F5FC-48B8-AC07-4DC12C01215D}" 7 | ProjectSection(SolutionItems) = preProject 8 | README.md = README.md 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartSolrSchema", "src\SmartSolrSchema\SmartSolrSchema.csproj", "{34C224F9-63EB-4D3B-A50D-4D4C185EDD1B}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {34C224F9-63EB-4D3B-A50D-4D4C185EDD1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {34C224F9-63EB-4D3B-A50D-4D4C185EDD1B}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {34C224F9-63EB-4D3B-A50D-4D4C185EDD1B}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {34C224F9-63EB-4D3B-A50D-4D4C185EDD1B}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {683C6893-BEAE-40BE-A611-050AEC6B9DC7} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /build/PackageNuGet.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | 3 | SET scriptRoot=%~dp0 4 | 5 | powershell.exe -ExecutionPolicy Unrestricted -NoExit .\PackageNuGet.ps1 %scriptRoot% -------------------------------------------------------------------------------- /build/PackageNuGet.ps1: -------------------------------------------------------------------------------- 1 | param($scriptRoot) 2 | 3 | $ErrorActionPreference = "Stop" 4 | 5 | function Resolve-MsBuild { 6 | $msb2017 = Resolve-Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\*\MSBuild\*\bin\msbuild.exe" -ErrorAction SilentlyContinue 7 | if($msb2017) { 8 | Write-Host "Found MSBuild 2019 (or later)." 9 | Write-Host $msb2017 10 | return $msb2017 11 | } 12 | 13 | $msBuild2015 = "${env:ProgramFiles(x86)}\MSBuild\14.0\bin\msbuild.exe" 14 | 15 | if(-not (Test-Path $msBuild2015)) { 16 | throw 'Could not find MSBuild 2015 or later.' 17 | } 18 | 19 | Write-Host "Found MSBuild 2015." 20 | Write-Host $msBuild2015 21 | 22 | return $msBuild2015 23 | } 24 | 25 | $msBuild = Resolve-MsBuild 26 | $nuGet = "$scriptRoot..\tools\NuGet.exe" 27 | $solution = "$scriptRoot\..\SmartSolrSchema.sln" 28 | 29 | & $nuGet restore $solution 30 | & $msBuild $solution /p:Configuration=Release /t:Rebuild /m 31 | 32 | & dotnet pack "$scriptRoot\..\src\SmartSolrSchema\SmartSolrSchema.csproj" -Property:Configuration=Release -o $scriptRoot --include-symbols -------------------------------------------------------------------------------- /build/Push NuGet.ps1: -------------------------------------------------------------------------------- 1 | gci *.nupkg -exclude *.symbols.nupkg | % { ..\tools\NuGet.exe push $_ -Source https://api.nuget.org/v3/index.json } -------------------------------------------------------------------------------- /src/SmartSolrSchema/App_Config/Modules/SmartSolrSchema/DateTimeSortable.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/SmartSolrSchema/App_Config/Modules/SmartSolrSchema/PopulateSolrSchema.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | SmartSolrSchema.Pipelines.SolrProvider.DefaultPopulateHelperFactory, SmartSolrSchema 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/SmartSolrSchema/ComputedFields/GenericDateField.cs: -------------------------------------------------------------------------------- 1 | using Sitecore.ContentSearch; 2 | using Sitecore.ContentSearch.ComputedFields; 3 | using Sitecore.Data.Fields; 4 | 5 | namespace SmartSolrSchema.ComputedFields 6 | { 7 | public class GenericDateField : IComputedIndexField 8 | { 9 | public string FieldName { get; set; } 10 | public string ReturnType { get; set; } 11 | 12 | public object ComputeFieldValue(IIndexable indexable) 13 | { 14 | var indexItem = indexable as SitecoreIndexableItem; 15 | if (indexItem?.Item == null) return null; 16 | var item = indexItem.Item; 17 | 18 | if (string.IsNullOrEmpty(item.Fields[FieldName]?.Value)) return null; 19 | 20 | DateField fieldValue = item.Fields[FieldName]; 21 | if ((fieldValue == null) || (fieldValue.InnerField.Value == null)) return null; 22 | 23 | return fieldValue.DateTime; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/SmartSolrSchema/Pipelines/SolrProvider/PopulateSolrSchemaHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Xml.Linq; 4 | using Sitecore.Configuration; 5 | using Sitecore.ContentSearch.SolrProvider.Pipelines.PopulateSolrSchema; 6 | using Sitecore.Diagnostics; 7 | using SolrNet.Schema; 8 | 9 | namespace SmartSolrSchema.Pipelines.SolrProvider 10 | { 11 | public class PopulateSolrSchemaHelper : ISchemaPopulateHelper 12 | { 13 | private readonly SolrSchema solrSchema; 14 | 15 | public PopulateSolrSchemaHelper(SolrSchema solrSchema) 16 | { 17 | Assert.ArgumentNotNull(solrSchema, "solrSchema"); 18 | this.solrSchema = solrSchema; 19 | } 20 | 21 | public virtual IEnumerable GetAllFields() 22 | { 23 | var addFields = GetAddFields().Where(x => x != null).ToList(); 24 | var langSpecific = GetAddFieldsLangSpecific(addFields); 25 | return from o in GetRemoveFields().Union(addFields).Union(langSpecific) 26 | where o != null 27 | select o; 28 | } 29 | 30 | public virtual IEnumerable GetAllFieldTypes() 31 | { 32 | return from o in GetReplaceFields().Union(GetAddFieldTypes()) 33 | where o != null 34 | select o; 35 | } 36 | 37 | protected virtual bool TypeExists(string type) 38 | { 39 | return solrSchema.FindSolrFieldTypeByName(type) != null; 40 | } 41 | 42 | protected XElement CreateField(string name, string type, bool required, bool indexed, bool stored, bool multiValued, bool omitNorms, bool termVectors, bool termPositions, bool termOffsets, string defaultValue = null, bool isDynamic = false) 43 | { 44 | if (!TypeExists(type)) 45 | { 46 | return null; 47 | } 48 | 49 | string expandedName = isDynamic ? "add-dynamic-field" : "add-field"; 50 | XElement xElement = new XElement(expandedName); 51 | xElement.Add(new XElement("name", name)); 52 | xElement.Add(new XElement("type", type)); 53 | xElement.Add(new XElement("indexed", indexed.ToString().ToLowerInvariant())); 54 | xElement.Add(new XElement("stored", stored.ToString().ToLowerInvariant())); 55 | if (required) 56 | { 57 | xElement.Add(new XElement("required", true)); 58 | } 59 | 60 | if (multiValued) 61 | { 62 | xElement.Add(new XElement("multiValued", true)); 63 | } 64 | 65 | if (omitNorms) 66 | { 67 | xElement.Add(new XElement("omitNorms", true)); 68 | } 69 | 70 | if (termVectors) 71 | { 72 | xElement.Add(new XElement("termVectors", true)); 73 | } 74 | 75 | if (termPositions) 76 | { 77 | xElement.Add(new XElement("termPositions", true)); 78 | } 79 | 80 | if (termOffsets) 81 | { 82 | xElement.Add(new XElement("termOffsets", true)); 83 | } 84 | 85 | if (!string.IsNullOrEmpty(defaultValue)) 86 | { 87 | xElement.Add(new XElement("default", defaultValue)); 88 | } 89 | 90 | return xElement; 91 | } 92 | 93 | protected XElement CreateFieldType(string name, string @class, IDictionary properties) 94 | { 95 | XElement xElement = new XElement(TypeExists(name) ? "replace-field-type" : "add-field-type"); 96 | xElement.Add(new XElement("name", name)); 97 | xElement.Add(new XElement("class", @class)); 98 | foreach (KeyValuePair property in properties) 99 | { 100 | xElement.Add(new XElement(property.Key, property.Value)); 101 | } 102 | 103 | return xElement; 104 | } 105 | 106 | private static XElement GetRemoveField(string name, bool isDynamicField = false) 107 | { 108 | Assert.ArgumentNotNull(name, "name"); 109 | XElement xElement = new XElement(isDynamicField ? "delete-dynamic-field" : "delete-field"); 110 | xElement.Add(new XElement("name", name)); 111 | return xElement; 112 | } 113 | 114 | private static XElement GetRemoveCopyField(string source, string destination) 115 | { 116 | Assert.ArgumentNotNull(source, "source"); 117 | Assert.ArgumentNotNull(destination, "destination"); 118 | XElement xElement = new XElement("delete-copy-field"); 119 | xElement.Add(new XElement("source", source)); 120 | xElement.Add(new XElement("dest", destination)); 121 | return xElement; 122 | } 123 | 124 | private IEnumerable GetRemoveFields() 125 | { 126 | foreach (SolrNet.Schema.SolrCopyField solrCopyField in solrSchema.SolrCopyFields) 127 | { 128 | yield return GetRemoveCopyField(solrCopyField.Source, solrCopyField.Destination); 129 | } 130 | 131 | foreach (SolrDynamicField solrDynamicField in solrSchema.SolrDynamicFields) 132 | { 133 | yield return GetRemoveField(solrDynamicField.Name, isDynamicField: true); 134 | } 135 | 136 | foreach (SolrField solrField in solrSchema.SolrFields) 137 | { 138 | yield return GetRemoveField(solrField.Name); 139 | } 140 | } 141 | 142 | private IEnumerable GetAddFieldTypes() 143 | { 144 | yield return CreateFieldType("random", "solr.RandomSortField", new Dictionary 145 | { 146 | { 147 | "indexed", 148 | "true" 149 | } 150 | }); 151 | yield return CreateFieldType("ignored", "solr.StrField", new Dictionary 152 | { 153 | { 154 | "indexed", 155 | "false" 156 | }, 157 | { 158 | "stored", 159 | "false" 160 | }, 161 | { 162 | "docValues", 163 | "false" 164 | }, 165 | { 166 | "multiValued", 167 | "true" 168 | } 169 | }); 170 | } 171 | 172 | private IEnumerable GetAddFields() 173 | { 174 | yield return CreateField("_content", "text_general", required: false, indexed: true, stored: false, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 175 | yield return CreateField("_database", "lowercase", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 176 | yield return CreateField("_path", "string", required: false, indexed: true, stored: true, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 177 | yield return CreateField("_uniqueid", "string", required: true, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 178 | yield return CreateField("_datasource", "lowercase", required: true, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 179 | yield return CreateField("_parent", "string", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 180 | yield return CreateField("_name", "text_general", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 181 | yield return CreateField("_displayname", "text_general", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 182 | yield return CreateField("_language", "string", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 183 | yield return CreateField("_creator", "lowercase", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 184 | yield return CreateField("_editor", "lowercase", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 185 | yield return CreateField("_created", "pdate", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 186 | yield return CreateField("_updated", "pdate", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 187 | yield return CreateField("_hidden", "boolean", required: false, indexed: true, stored: false, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 188 | yield return CreateField("_template", "lowercase", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 189 | yield return CreateField("_templatename", "lowercase", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 190 | yield return CreateField("_templates", "string", required: false, indexed: true, stored: true, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 191 | yield return CreateField("_icon", "lowercase", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 192 | yield return CreateField("_links", "lowercase", required: false, indexed: true, stored: true, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 193 | yield return CreateField("_tags", "lowercase", required: false, indexed: true, stored: true, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 194 | yield return CreateField("_group", "string", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 195 | yield return CreateField("_indexname", "lowercase", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 196 | yield return CreateField("_latestversion", "boolean", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 197 | yield return CreateField("_indextimestamp", "pdate", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, "NOW"); 198 | yield return CreateField("_fullpath", "lowercase", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 199 | yield return CreateField("_isclone", "boolean", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 200 | yield return CreateField("_version", "string", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 201 | yield return CreateField("_hash", "string", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 202 | yield return CreateField("__semantics", "string", required: false, indexed: true, stored: true, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 203 | yield return CreateField("__boost", "pfloat", required: false, indexed: true, stored: true, multiValued: false, omitNorms: true, termVectors: false, termPositions: false, termOffsets: false, "0"); 204 | yield return CreateReadAccessField(); 205 | yield return CreateField("lock", "boolean", required: false, indexed: true, stored: false, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 206 | yield return CreateField("__bucketable", "boolean", required: false, indexed: true, stored: false, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 207 | yield return CreateField("__workflow_state", "string", required: false, indexed: true, stored: false, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 208 | yield return CreateField("__is_bucket", "boolean", required: false, indexed: true, stored: false, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 209 | yield return CreateField("is_displayed_in_search_results", "boolean", required: false, indexed: true, stored: false, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 210 | yield return CreateField("text", "text_general", required: false, indexed: true, stored: false, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 211 | yield return CreateField("text_rev", "text_general_rev", required: false, indexed: true, stored: false, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 212 | yield return CreateField("alphaNameSort", "alphaOnlySort", required: false, indexed: true, stored: false, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 213 | yield return CreateField("__hidden", "boolean", required: false, indexed: true, stored: false, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 214 | yield return CreateField("_version_", "plong", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 215 | yield return CreateField("*_t", "text_general", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 216 | yield return CreateField("*_t_en", "text_en", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 217 | yield return CreateField("*_t_ar", "text_ar", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 218 | yield return CreateField("*_t_bg", "text_bg", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 219 | yield return CreateField("*_t_ca", "text_ca", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 220 | yield return CreateField("*_t_cs", "text_cz", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 221 | yield return CreateField("*_t_da", "text_da", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 222 | yield return CreateField("*_t_de", "text_de", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 223 | yield return CreateField("*_t_el", "text_el", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 224 | yield return CreateField("*_t_es", "text_es", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 225 | yield return CreateField("*_t_eu", "text_eu", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 226 | yield return CreateField("*_t_fa", "text_fa", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 227 | yield return CreateField("*_t_fi", "text_fi", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 228 | yield return CreateField("*_t_fr", "text_fr", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 229 | yield return CreateField("*_t_ga", "text_ga", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 230 | yield return CreateField("*_t_gl", "text_gl", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 231 | yield return CreateField("*_t_hi", "text_hi", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 232 | yield return CreateField("*_t_hu", "text_hu", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 233 | yield return CreateField("*_t_hy", "text_hy", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 234 | yield return CreateField("*_t_id", "text_id", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 235 | yield return CreateField("*_t_it", "text_it", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 236 | yield return CreateField("*_t_ja", "text_ja", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 237 | yield return CreateField("*_t_lv", "text_lv", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 238 | yield return CreateField("*_t_nl", "text_nl", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 239 | yield return CreateField("*_t_nb", "text_no", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 240 | yield return CreateField("*_t_pt", "text_pt", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 241 | yield return CreateField("*_t_ro", "text_ro", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 242 | yield return CreateField("*_t_ru", "text_ru", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 243 | yield return CreateField("*_t_sv", "text_sv", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 244 | yield return CreateField("*_t_th", "text_th", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 245 | yield return CreateField("*_t_tr", "text_tr", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 246 | yield return CreateField("*_i", "pint", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 247 | yield return CreateField("*_s", "string", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 248 | yield return CreateField("*_sm", "string", required: false, indexed: true, stored: true, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 249 | yield return CreateField("*_ls", "lowercase", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 250 | yield return CreateField("*_lsm", "lowercase", required: false, indexed: true, stored: true, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 251 | yield return CreateField("*_im", "pint", required: false, indexed: true, stored: true, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 252 | yield return CreateField("*_txm", "text_general", required: false, indexed: true, stored: true, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 253 | yield return CreateField("*_b", "boolean", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 254 | yield return CreateField("*_dt", "pdate", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 255 | yield return CreateField("*_p", "location", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 256 | yield return CreateField("*_ti", "pint", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 257 | yield return CreateField("*_tl", "plong", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 258 | yield return CreateField("*_tf", "pfloat", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 259 | yield return CreateField("*_td", "pdouble", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 260 | yield return CreateField("*_tdt", "pdate", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 261 | yield return CreateField("*_tdts", "pdate", required: false, indexed: true, stored: false, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 262 | yield return CreateField("*_tdtm", "pdate", required: false, indexed: true, stored: true, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 263 | yield return CreateField("*_pi", "pint", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 264 | yield return CreateField("*_c", "currency", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 265 | yield return CreateField("*_ignored", "ignored", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 266 | yield return CreateField("*_random", "random", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 267 | yield return CreateField("*_rpt", "location_rpt", required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 268 | } 269 | 270 | private List GetAddFieldsLangSpecific(List addFields) 271 | { 272 | var langs = Factory.GetDatabase("master") 273 | .GetItem("/sitecore/system/Languages") 274 | .Children 275 | .Select(x => x["Iso"]) 276 | .Where(x => !string.IsNullOrEmpty(x)) 277 | .Select(x => $"*_t_{x}") 278 | .ToList() 279 | .Distinct(); 280 | var addFieldsLangs = addFields 281 | .Select(x => x.Element("name")?.Value) 282 | .Where(x => !string.IsNullOrEmpty(x)) 283 | .ToList(); 284 | var toAdd = langs 285 | .Where(x => !addFieldsLangs.Contains(x)) 286 | .ToList(); 287 | var result = new List(); 288 | foreach (var fieldname in toAdd) 289 | { 290 | var lang = fieldname.Replace("*_t_", ""); 291 | result.Add(CreateDynamicFieldWithFallbackFieldType(fieldname, $"text_{lang}")); 292 | Sitecore.Diagnostics.Log.Info($"SmartSolrSchema: adding custom defined language to schema {lang}", this); 293 | } 294 | return result; 295 | } 296 | 297 | private XElement CreateReadAccessField() 298 | { 299 | XElement xElement = CreateField("_readaccess", "lowercase", required: false, indexed: true, stored: false, multiValued: true, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false); 300 | xElement.Add(new XElement("docValues", false)); 301 | return xElement; 302 | } 303 | 304 | private XElement CreateDynamicFieldWithFallbackFieldType(string fieldName, string fieldType, string fallbackFieldType = "text_general") 305 | { 306 | Assert.ArgumentNotNull(fieldName, "fieldName"); 307 | Assert.ArgumentNotNull(fieldType, "fieldType"); 308 | string type = TypeExists(fieldType) ? fieldType : fallbackFieldType; 309 | return CreateField(fieldName, type, required: false, indexed: true, stored: true, multiValued: false, omitNorms: false, termVectors: false, termPositions: false, termOffsets: false, null, isDynamic: true); 310 | } 311 | 312 | private IEnumerable GetReplaceFields() 313 | { 314 | yield return GetReplaceTextGeneralFieldType(); 315 | } 316 | 317 | private XElement GetReplaceTextGeneralFieldType() 318 | { 319 | SolrFieldType solrFieldType = solrSchema.SolrFieldTypes.Find((SolrFieldType f) => f.Name == "text_general"); 320 | if (solrFieldType == null) 321 | { 322 | return null; 323 | } 324 | 325 | XElement xElement = new XElement("replace-field-type"); 326 | xElement.Add(new XElement("name", solrFieldType.Name)); 327 | xElement.Add(new XElement("class", solrFieldType.Type)); 328 | xElement.Add(new XElement("positionIncrementGap", "100")); 329 | xElement.Add(new XElement("multiValued", "false")); 330 | XElement xElement2 = new XElement("indexAnalyzer"); 331 | xElement2.Add(new XElement("tokenizer", new XElement("class", "solr.StandardTokenizerFactory"))); 332 | xElement2.Add(new XElement("filters", new XElement("class", "solr.StopFilterFactory"), new XElement("ignoreCase", "true"), new XElement("words", "stopwords.txt"))); 333 | xElement2.Add(new XElement("filters", new XElement("class", "solr.LowerCaseFilterFactory"))); 334 | xElement.Add(xElement2); 335 | XElement xElement3 = new XElement("queryAnalyzer"); 336 | xElement3.Add(new XElement("tokenizer", new XElement("class", "solr.StandardTokenizerFactory"))); 337 | xElement3.Add(new XElement("filters", new XElement("class", "solr.StopFilterFactory"), new XElement("ignoreCase", "true"), new XElement("words", "stopwords.txt"))); 338 | xElement3.Add(new XElement("filters", new XElement("class", "solr.SynonymFilterFactory"), new XElement("synonyms", "synonyms.txt"), new XElement("ignoreCase", "true"), new XElement("expand", "true"))); 339 | xElement3.Add(new XElement("filters", new XElement("class", "solr.LowerCaseFilterFactory"))); 340 | xElement.Add(xElement3); 341 | return xElement; 342 | } 343 | } 344 | 345 | #if NET48 346 | 347 | public class DefaultPopulateHelperFactory : Sitecore.ContentSearch.SolrProvider.Abstractions.IPopulateHelperFactory 348 | { 349 | public ISchemaPopulateHelper GetPopulateHelper(SolrSchema solrSchema) => new PopulateSolrSchemaHelper(solrSchema); 350 | } 351 | 352 | #else 353 | 354 | public class PopulateFields : Sitecore.ContentSearch.SolrProvider.Pipelines.PopulateSolrSchema.PopulateFields 355 | { 356 | protected override ISchemaPopulateHelper GetHelper(SolrSchema solrSchema) => new PopulateSolrSchemaHelper(solrSchema); 357 | } 358 | 359 | #endif 360 | } -------------------------------------------------------------------------------- /src/SmartSolrSchema/SmartSolrSchema.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48 5 | false 6 | 1.1.2 7 | Mark Gibbons 8 | 9 | MIT 10 | https://github.com/dataweaversio/SmartSolrSchema 11 | 12 | sitecore solr 13 | Copyright Dataweavers 14 | 15 | 16 | SmartSolrSchema 17 | SmartSolrSchema 18 | Dataweavers 19 | 20 | SmartSolrSchema.SC101 21 | Smart Solr Schema Populate Module for Sitecore 10.1, 10.2, 10.3 22 | git 23 | 24 | 25 | 26 | 10.1.2 27 | all 28 | 29 | 30 | 10.1.2 31 | all 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /tools/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dataweaversio/SmartSolrSchema/114483554b5767e81b9195228bbc674f5c85ba32/tools/NuGet.exe --------------------------------------------------------------------------------