diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a69312 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +src/.vs/ +src/packages/ +src/Sitecore.Support.AdvancedSearchList/bin/ +src/Sitecore.Support.AdvancedSearchList/obj/ +*.pubxml +*.user diff --git a/ASL.png b/ASL.png deleted file mode 100644 index c7e6546..0000000 Binary files a/ASL.png and /dev/null differ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 3886f0c..0000000 --- a/LICENSE +++ /dev/null @@ -1,141 +0,0 @@ -Sitecore Corporation A/S License for GitHub - -Copyright © 2016 Sitecore Corporation A/S. All rights reserved. - -1. This Agreement. Any use of the content provided on GitHub by Sitecore - (“Materials”) shall be governed by this license (“Agreement”). This - Agreement shall apply to any person or application for each access or any - other use of the Materials with any device or in any form (“You” or “Your”). - You may not and will immediately discontinue any use or access of the - Materials if you do not acknowledge and fully agree with this License. - -2. Materials. Sitecore grants you a limited, temporary, non-exclusive, - non-transferable, non-assignable, and non-sublicensable license to access - and use the Materials in compliance with all applicable laws and this - Agreement. Ownership of the Materials and all worldwide rights, title and - interest in and to any intellectual property associated with the Materials - shall remain solely and exclusively with Sitecore or the respective owners - of such intellectual property. You acknowledge and agree no intellectual - property rights of any kind, whether now known or later developed, are - transferred under this Agreement to You. The Materials provided here are - for reference purposes only. - -3. Your Input. You may transmit or publish content, suggestions, enhancement - requests, recommendations or other feedback created by You (“User Content”). - You do not transfer any ownership right in the User Content and you hereby - grant Sitecore a non-exclusive, royalty-free, worldwide, transferable, - sublicensable, irrevocable, perpetual license to use, copy (in whole or - in part), modify, display in any form now known or hereinafter developed, - distribute, and make derivative works of, including by incorporating into - any product or service owned by Sitecore, any User Content provided by You - relating to any product or service owned by Sitecore. You are solely - responsible User Content and the consequences of its transmission or - publication. - -4. Restrictions. You agree not to (a) reverse engineer or attempt to derive - the source code from or create derivative works of the Materials, or any - portion thereof; (b) use the Materials in a greater capacity than - authorized under this Agreement; (c) sublicense, distribute or pledge - the Materials, (d) access, use, or copy any portion of the Materials - to directly or indirectly to develop, promote or support any product or - service that is competitive with Sitecore, (e) lease, rent or commercially - share or otherwise use the Materials for purposes of providing a service - bureau or providing third party hosting, application service provider type - services; (f) remove any identification, patent, trademark, copyright, or - other notice from the Materials; (g) attempt to gain unauthorized access - to the Materials or the related systems or networks; (h) use any name, - mark, or designation of Sitecore, or any of its affiliates or licensors or - their respective products or services, unless expressly permitted herein or - by Sitecore in writing; or (i) use the Materials in any manner that brings - any Sitecore brands, trademarks, products or services into disrepute. - -5. Modification of Terms. Sitecore reserves the right to modify this Agreement - at any time. Modifications to this Agreement are effective immediately and - shall apply upon Your continued access or use of the Materials. - -6. DISCLAIMER OF WARRANTY. YOU EXPRESSLY UNDERSTAND AND AGREE THAT: - - a. YOUR USE OF THE MATERIALS IS AT YOUR SOLE RISK. THE MATERIALS PROVIDED - ON AN “AS IS” AND “AS AVAILABLE” BASIS. SITECORE EXPRESSLY DISCLAIMS - ALL WARRANTIES OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT - NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR - A PARTICULAR PURPOSE AND NON-INFRINGEMENT. - - b. SITECORE WILL MAKE REASONABLE EFFORTS TO MAINTAIN THE MATERIALS, - HOWEVER, SITECORE IS NOT RESPONSIBLE FOR ANY DAMAGE, LOSS OF DATA, - INFORMATION, REVENUE, OR OTHER HARM TO BUSINESS ARISING OUT OF DELAYS, - SITE INTERRUPTION, MISDELIVERY OR NONDELIVERY OF INFORMATION, - RESTRICTION OR LOSS OF ACCESS, BUGS OR OTHER ERRORS, UNAUTHORIZED USE - DUE TO YOUR SHARING OF ACCESS TO THE SERVICE, OR OTHER INTERACTION WITH - THE SERVICE. YOU ARE RESPONSIBLE FOR MAINTAINING AND BACKING-UP YOUR - DATA AND INFORMATION THAT MAY RESIDE ON THE SERVICE. SITECORE DOES NOT - WARRANT THAT: - - i. THE MATERIALS WILL MEET YOUR SPECIFIC REQUIREMENTS; - ii. ACCESS TO THE MATERIALS WILL BE UNINTERRUPTED, TIMELY, SECURE, - OR THAT THE MATERIALS WILL BE ERROR-FREE; - iii. THE RESULTS THAT MAY BE OBTAINED FROM THE USE OF THE MATERIALS - WILL BE ACCURATE OR RELIABLE; - iv. THE QUALITY OF ANY PRODUCTS, SERVICES, INFORMATION, OR OTHER - MATERIAL PURCHASED OR OBTAINED BY YOU THROUGH THE MATERIALS WILL - MEET YOUR EXPECTATIONS; OR - v. ANY ERRORS IN THE MATERIALS WILL BE CORRECTED. - - c. ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE - MATERIALS IS DONE AT YOUR OWN DISCRETION AND RISK AND THAT YOU WILL BE - SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR LOSS OF - DATA THAT RESULTS FROM THE DOWNLOAD OF ANY SUCH MATERIAL. - - d. NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED THROUGH OR - FROM THE MATERIAL SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED IN - THESE TERMS, INCLUDING ANY STATEMENT BY A SITECORE EMPLOYEE, AFFILIATE, - DIRECTOR, OFFICER, OR AGENT. - - e. THE DISCLAIMERS IN THIS SECTION OF ANY EXPRESS OR IMPLIED WARRANTY BY - SITECORE SHALL INCLUDE WITHOUT LIMITATION SITECORE’S PARENTS, - SUBSIDIARIES, AFFILIATES, DIRECTORS, EMPLOYEES, AND AGENTS. - -7. LIMTIATION OF LIABILITY. YOU EXPRESSLY UNDERSTAND AND AGREE THAT SITECORE - SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - CONSEQUENTIAL OR EXEMPLARY DAMAGES, INCLUDING BUT NOT LIMITED TO, DAMAGES - FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER INTANGIBLE LOSSES - (EVEN IF SITECORE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), - RESULTING FROM: - - a. THE USE OR THE INABILITY TO USE THE MATERIALS; - b. THE COST OF PROCUREMENT OF SUBSTITUTE GOODS AND SERVICES RESULTING FROM - THE USE OF MATERIALS; OR - c. UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA. - -8. EXCLUSIONS AND LIMITATIONS. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF - CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR - INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE - LIMITATIONS MAY NOT APPLY TO YOU AND SHALL ONLY APPLY TO THE FULLEST EXTENT - PERMITTED UNDER THE APPLICABLE LAW(S). - -9. Export Control. Sitecore’s software, services, and content in the Materials - may be subject to U.S. and international trade and export control laws. - You agree to comply with all such laws. - -10. Governing Law and Process. The English version of this Agreement shall be - used when interpreting and applying the provisions herein. If You reside in - the U.S., You irrevocably consent to the exclusive jurisdiction and venue - of the state and federal courts located in San Francisco County of - California in accordance with the laws of the State of California. - If You reside outside the U.S., You irrevocably consent to the exclusive - jurisdiction and venue of the City Court of Copenhagen, Denmark in - accordance with the laws of Denmark. This paragraph applies without - reference to conflict of laws principles and excludes the UN Convention on - Contracts for the International Sale of Goods and the Uniform Computer - Information Transaction Act. In any action to enforce this Agreement, - the prevailing party will be entitled to costs and attorneys’ fees. - -11. Agreement. This Agreement constitutes the entire agreement between the - parties hereto pertaining to the subject matter hereof, and any and all - written or oral agreements heretofore existing between the parties hereto - are expressly canceled. This Agreement is not assignable, transferable or - sub-licensable by you except with prior written consent. Any heading, - caption or section title contained in this Agreement is inserted only as - a matter of convenience and in no way defines or explains any section or - provision hereof. Sitecore reserves the right in its sole discretion - to terminate the use of the Materials by a user at any time. \ No newline at end of file diff --git a/readme.md b/readme.md index 8be1ef0..3694795 100644 --- a/readme.md +++ b/readme.md @@ -1,17 +1,3 @@ # Sitecore.Support.AdvancedSearchList -A field type which can be used as an alternative to the Multilist With Search -## Description -The new field type allows you to select an ordered list of items using a more convenient search dialog with extended filtering features (like facet search or search by a field): -![ASL_screenshot](ASL.png) - -The new field type supports the same data source parameters as `Multilist With Search`, but also includes: -* **EnableSetNewStartLocation** - allows dynamic selection of the search location. This functionality is available in the new field type by default. -* **PageSize** - used to enable paging. Is not required for the new field type as it uses continuous scroll bar. -* **StartSearchLocation** - doesn't support fast queries. - -## License -This patch is licensed under the [Sitecore Corporation A/S License for GitHub](https://github.com/sitecoresupport/Sitecore.Support.AdvancedSearchList/blob/master/LICENSE). - -## Download -Downloads are available via [GitHub Releases](https://github.com/sitecoresupport/Sitecore.Support.AdvancedSearchList/releases). +See [master branch](https://github.com/sitecoresupport/Sitecore.Support.AdvancedSearchList) for details. \ No newline at end of file diff --git a/src/Sitecore.Support.AdvancedSearchList.sln b/src/Sitecore.Support.AdvancedSearchList.sln new file mode 100644 index 0000000..ec54f5d --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sitecore.Support.AdvancedSearchList", "Sitecore.Support.AdvancedSearchList\Sitecore.Support.AdvancedSearchList.csproj", "{B72AA20D-2412-45A1-AC2F-8B5E3261001E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B72AA20D-2412-45A1-AC2F-8B5E3261001E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B72AA20D-2412-45A1-AC2F-8B5E3261001E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B72AA20D-2412-45A1-AC2F-8B5E3261001E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B72AA20D-2412-45A1-AC2F-8B5E3261001E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/Sitecore.Support.AdvancedSearchList/App_Config/Include/zzz/Sitecore.Support.Asl.config b/src/Sitecore.Support.AdvancedSearchList/App_Config/Include/zzz/Sitecore.Support.Asl.config new file mode 100644 index 0000000..06e0f2a --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/App_Config/Include/zzz/Sitecore.Support.Asl.config @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Sitecore.Support.AdvancedSearchList/App_Config/Include/zzz/Sitecore.Support.MultilistExtendedSource.config.disabled b/src/Sitecore.Support.AdvancedSearchList/App_Config/Include/zzz/Sitecore.Support.MultilistExtendedSource.config.disabled new file mode 100644 index 0000000..fac73cb --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/App_Config/Include/zzz/Sitecore.Support.MultilistExtendedSource.config.disabled @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/Sitecore.Support.AdvancedSearchList/GetLookupSourceItems/ProcessMultilistWithSearchSource.cs b/src/Sitecore.Support.AdvancedSearchList/GetLookupSourceItems/ProcessMultilistWithSearchSource.cs new file mode 100644 index 0000000..782d9a2 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/GetLookupSourceItems/ProcessMultilistWithSearchSource.cs @@ -0,0 +1,120 @@ + +namespace Sitecore.Support.Pipelines.GetLookupSourceItems +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Sitecore.Collections; + using Sitecore.Configuration; + using Sitecore.ContentSearch; + using Sitecore.ContentSearch.Diagnostics; + using Sitecore.ContentSearch.SearchTypes; + using Sitecore.ContentSearch.Security; + using Sitecore.ContentSearch.Utilities; + using Sitecore.Data; + using Sitecore.Data.Items; + using Sitecore.Diagnostics; + using Sitecore.Pipelines.GetLookupSourceItems; + using Sitecore.SecurityModel; + using Sitecore.Support.ASL.Datasource; + + public class ProcessMultilistWithSearchSource + { + private const string IndexPrefix = "index:"; + + public void Process(GetLookupSourceItemsArgs args) + { + Assert.ArgumentNotNull(args, "args"); + + if (!args.Source.StartsWith(IndexPrefix, StringComparison.InvariantCulture)) + { + return; + } + + var sourceValue = args.Source.Substring(IndexPrefix.Length); + + var sourceParser = new MlsSourceToSearchStringModel(); + + string indexName; + ID rootItemId; + var model = sourceParser.BuildSearchStringModel(args.Item, sourceValue, out indexName, out rootItemId); + + var index = this.ResolveIndex(indexName, rootItemId); + + + using (IProviderSearchContext context = index.CreateSearchContext(SearchSecurityOptions.Default)) + { + var source = LinqHelper.CreateQuery(context, model) as IEnumerable; + + if (Settings.GetBoolSetting("ContentSearch.EnableSearchDebug", false)) + { + var sourceArray = source.ToArray(); + if (sourceArray.Length > 100) + { + SearchLog.Log.Info("SUPPORT Index returns more than 100 documents for the current data source."); + } + + source = sourceArray; + } + + // TODO Check if latest version is applied and only items are applied + var uniqueItems = this.OptimizedDistinctWithKeptOrder(source).ToArray(); + args.Result.AddRange(uniqueItems); + args.AbortPipeline(); + } + } + + protected virtual ISearchIndex ResolveIndex(string indexName, ID rootItemID) + { + if (!string.IsNullOrWhiteSpace(indexName)) + { + return ContentSearchManager.GetIndex(indexName); + } + + using (new SecurityDisabler()) + { + var item = Context.ContentDatabase.GetItem(rootItemID); + return ContentSearchManager.GetIndex((SitecoreIndexableItem)item); + } + } + + protected IEnumerable OptimizedDistinctWithKeptOrder(IEnumerable enumerable) + { + Assert.ArgumentNotNull(enumerable, "enumerable"); + var proccessedElements = new Set(); + + foreach (var element in enumerable) + { + if (element == null || proccessedElements.Contains(element.Id)) + { + continue; + } + + proccessedElements.Add(element.Id); + + // Applying security + var item = element.GetItem(); + + if (item == null) + { + continue; + } + + yield return item; + } + } + + + protected class MlsSourceToSearchStringModel : MlsDataSource + { + public List BuildSearchStringModel (Item dataItem, string sourceValue, out string indexName, out ID rootItemId) + { + Assert.ArgumentNotNull(dataItem, "dataItem"); + Assert.ArgumentNotNull(sourceValue, "sourceValue"); + + var dsParams = this.BuildNameValueCollection(sourceValue); + return this.ExtractFilters(dsParams, q => dataItem.Axes.SelectSingleItem(q), out rootItemId, out indexName); + } + } + } +} diff --git a/src/Sitecore.Support.AdvancedSearchList/Properties/AssemblyInfo.cs b/src/Sitecore.Support.AdvancedSearchList/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..299724b --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Sitecore.Support.AdvancedSearchList")] +[assembly: AssemblyProduct("Sitecore.Support.AdvancedSearchList")] +[assembly: ComVisible(false)] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Sitecore.Support.AdvancedSearchList/Sitecore.Support.AdvancedSearchList.csproj b/src/Sitecore.Support.AdvancedSearchList/Sitecore.Support.AdvancedSearchList.csproj new file mode 100644 index 0000000..d02f506 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/Sitecore.Support.AdvancedSearchList.csproj @@ -0,0 +1,140 @@ + + + + + Debug + AnyCPU + + + 2.0 + {B72AA20D-2412-45A1-AC2F-8B5E3261001E} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Sitecore.Support + Sitecore.Support.ASL + v4.7.1 + true + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + ..\packages\SC.Sitecore.Annotations.9.3.0\lib\Sitecore.Annotations.dll + False + + + ..\packages\SC.Sitecore.Buckets.9.3.0\lib\Sitecore.Buckets.dll + False + + + ..\packages\SC.Sitecore.Buckets.Client.9.3.0\lib\Sitecore.Buckets.Client.dll + False + + + ..\packages\SC.Sitecore.Client.9.3.0\lib\Sitecore.Client.dll + False + + + ..\packages\SC.Sitecore.ContentSearch.9.3.0\lib\Sitecore.ContentSearch.dll + False + + + ..\packages\SC.Sitecore.Kernel.9.3.0\lib\Sitecore.Kernel.dll + False + + + + + + + + + + + + + + + + + + ASLSelectItems.aspx + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 0 + / + http://localhost:53098/ + False + False + + + False + + + + + \ No newline at end of file diff --git a/src/Sitecore.Support.AdvancedSearchList/packages.config b/src/Sitecore.Support.AdvancedSearchList/packages.config new file mode 100644 index 0000000..4ce6f9c --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Sitecore.Support.AdvancedSearchList/packages/AdvancedSearchList.Items.zip b/src/Sitecore.Support.AdvancedSearchList/packages/AdvancedSearchList.Items.zip new file mode 100644 index 0000000..c19f08f Binary files /dev/null and b/src/Sitecore.Support.AdvancedSearchList/packages/AdvancedSearchList.Items.zip differ diff --git a/src/Sitecore.Support.AdvancedSearchList/packages/SwitchMLStoASL.zip b/src/Sitecore.Support.AdvancedSearchList/packages/SwitchMLStoASL.zip new file mode 100644 index 0000000..c8097ed Binary files /dev/null and b/src/Sitecore.Support.AdvancedSearchList/packages/SwitchMLStoASL.zip differ diff --git a/src/Sitecore.Support.AdvancedSearchList/packages/xml/AdvancedSearchList.xml b/src/Sitecore.Support.AdvancedSearchList/packages/xml/AdvancedSearchList.xml new file mode 100644 index 0000000..826c290 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/packages/xml/AdvancedSearchList.xml @@ -0,0 +1,68 @@ + + + + AdvancedSearchList + Ivan Domashchenko + + + + + + + + + + + + True + + + + /core/sitecore/system/Field types/List Types/Search List/{E0D20F2F-CE65-4FE1-814F-1725CE68AD7D}/invariant/0 + /core/sitecore/system/Field types/List Types/Search List/Menu/{54F322D0-F000-4A64-9BC1-4DD6666620DA}/invariant/0 + /core/sitecore/system/Field types/List Types/Search List/Menu/Deselect All/{191D9BF0-C44D-4FF6-91D6-C37A439E7246}/invariant/0 + /core/sitecore/system/Field types/List Types/Search List/Menu/Select Items/{700D234B-B10A-4B94-9D42-3194321169E2}/invariant/0 + + False + + + + + + + + Items + + + + /App_Config/Include/zzz/Sitecore.Support.Asl.config + /bin/Sitecore.Support.ASL.dll + /sitecore/shell/Applications/Buckets/ASL/AdvancedSearchList.js + /sitecore/shell/Applications/Buckets/ASL/ASLControl.js + /sitecore/shell/Applications/Buckets/ASLSelectItems.aspx + /sitecore/shell/Applications/Dialogs/ASL/ASLSelectItems.xml + /sitecore/shell/Themes/Standard/ASL/FixFor72.css + /sitecore/shell/Themes/Standard/ASL/16x16/delete.png + /sitecore/shell/Themes/Standard/ASL/16x16/navigate_down.png + /sitecore/shell/Themes/Standard/ASL/16x16/navigate_up.png + + + + / + + + + + + Files + + + + + + + + + + + diff --git a/src/Sitecore.Support.AdvancedSearchList/packages/xml/AdvancedSearchList_Items.xml b/src/Sitecore.Support.AdvancedSearchList/packages/xml/AdvancedSearchList_Items.xml new file mode 100644 index 0000000..6271f26 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/packages/xml/AdvancedSearchList_Items.xml @@ -0,0 +1,54 @@ + + + + AdvancedSearchList_Items + Ivan Domashchenko + + + + + + + + + + + + True + + + + /core/sitecore/system/Field types/List Types/Search List/{E0D20F2F-CE65-4FE1-814F-1725CE68AD7D}/invariant/0 + /core/sitecore/system/Field types/List Types/Search List/Menu/{54F322D0-F000-4A64-9BC1-4DD6666620DA}/invariant/0 + /core/sitecore/system/Field types/List Types/Search List/Menu/Deselect All/{191D9BF0-C44D-4FF6-91D6-C37A439E7246}/invariant/0 + /core/sitecore/system/Field types/List Types/Search List/Menu/Select Items/{700D234B-B10A-4B94-9D42-3194321169E2}/invariant/0 + + False + + + + + + + Undefined + Undefined + + + + + + + + + Items + + + + + + + + + + + diff --git a/src/Sitecore.Support.AdvancedSearchList/packages/xml/SwitchMLStoASL.xml b/src/Sitecore.Support.AdvancedSearchList/packages/xml/SwitchMLStoASL.xml new file mode 100644 index 0000000..30193da --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/packages/xml/SwitchMLStoASL.xml @@ -0,0 +1,57 @@ + + + + SwitchMLStoASL + + + + + + + + + + + + + True + + + + /core/sitecore/system/Field types/List Types/Multilist with Search/{71A08B10-43D2-40DC-AB1A-B9E916FA780A}/invariant/0 + /core/sitecore/system/Field types/List Types/Multilist with Search/Menu/{976554D2-602A-4A90-AC56-D1D9BC1FFD13}/invariant/0 + /core/sitecore/system/Field types/List Types/Multilist with Search/Menu/Deselect All/{A09499F0-5258-4790-9CC2-29D5C7497260}/invariant/0 + /core/sitecore/system/Field types/List Types/Multilist with Search/Menu/Select Items/{58FEB815-BFA7-4704-BB56-CC1197994A81}/invariant/0 + /core/sitecore/system/Field types/List Types/Multilist with Search/MenuDisabled/{DB590140-D17E-46C5-B511-727E9042A21A}/invariant/0 + /core/sitecore/system/Field types/List Types/Multilist with Search/MenuDisabled/Deselect All/{EBAE6B75-2855-4379-A368-34F478974C69}/invariant/0 + /core/sitecore/system/Field types/List Types/Multilist with Search/MenuDisabled/Select All/{0E390141-DFDE-4F87-8E28-1864C0CB631F}/invariant/0 + + False + + + + + + + Undefined + Undefined + + + + + + + + + Items + + + + + + + + + + + diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASL/ASLControl.js b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASL/ASLControl.js new file mode 100644 index 0000000..50e53e8 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASL/ASLControl.js @@ -0,0 +1,51 @@ + +window.ASLC = window.ASLC || {}; + +ASLC.refreshValue = function (selectedField, valueInput) { + + var value = ""; + + for (var n = 0; n < selectedField.options.length; n++) { + var option = selectedField.options[n]; + value += (value != "" ? "|" : "") + option.value; + } + + valueInput.value = value; +} + +ASLC.deleteCurrent = function (fieldId) { + var selectedFieldId = "select#" + fieldId + "_selected"; + var selectedField = $sc(selectedFieldId); + + var selectedOptions = selectedField.children("option:selected"); + selectedField = selectedField[0]; + + if (selectedOptions.length > 0) { + var index = selectedOptions.index(); + selectedOptions.remove(); + + if (selectedField.length > 0) { + index = Math.min(Math.max(--index, 0), selectedField.length - 1); + } + + selectedField.selectedIndex = index; + } + + var valueId = "#" + fieldId + "_Value"; + var valueInput = $sc(valueId); + ASLC.refreshValue(selectedField, valueInput[0]); + + selectedField.focus(); +} + +ASLC.openCurrent = function (fieldId) { + var selectedFieldId = "select#" + fieldId + "_selected"; + var selectedField = $sc(selectedFieldId); + + var selectedOptions = selectedField.children("option:selected"); + + if (selectedOptions.length > 0) { + var id = selectedOptions[0].value; + window.scForm.postRequest('', '', '', 'contenteditor:launchtab(url=' + id + ')'); + } +} diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASL/AdvancedSearchList.js b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASL/AdvancedSearchList.js new file mode 100644 index 0000000..4bab075 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASL/AdvancedSearchList.js @@ -0,0 +1,174 @@ + +window.ASL = window.ASL || {}; + +ASL.validState = function() { + if (window.dataSourceViewType === undefined || window.currentBucketsViewType != window.dataSourceViewType) { + console.warn("ASL tries to use a wrong view type. "); + return false; + } + + if (window.selectedIds === undefined) { + console.warn("ASL can't find selected ids. "); + return false; + } + + return true; +} + +ASL.initSelectedIds = function (selectedIds) { + var self = {}; + self.sIds = selectedIds; + + self.add = function (id) { + if (this.sIds.indexOf(id) == -1) { + this.sIds.push(id); + return true; + } + + return false; + }; + + self.remove = function (id) { + var index = this.sIds.indexOf(id); + if (index >= 0) { + this.sIds.splice(index, 1); + } + }; + + return self; +} + +ASL.initSearchResult = function (selectedIds) { + var self = {}; + self.selectedIds = selectedIds; + + self.resultItems = function () { + return $j("#results").children(".BlogPostArea"); + }; + + self.isSelected = function (element) { + return $j(element).hasClass("highlight"); + } + + self.isElementOfId = function (element, id) { + return element.id == id; + } + + self.selectVersion = function (element) { + $j(element).addClass("highlight"); + } + + self.deselectVersion = function (element) { + $j(element).removeClass("highlight"); + } + + self.getItemElement = function (element) { + var el = $j(element); + if (el.is(".ceebox,.imgcontainer")) { + return null; + } + + return el.closest(".BlogPostArea")[0]; + } + + self.select = function (itemId, suppress) { + var _this = this; + this.resultItems().each(function (index, element) { + if (!_this.isElementOfId(element, itemId)) { + return; + } + _this.selectVersion(element); + }); + + if (!suppress) { + this.selectedIds.add(itemId); + } + + } + + self.deselect = function (itemId) { + var _this = this; + this.resultItems().each(function (index, element) { + if (!_this.isElementOfId(element, itemId)) { + return; + } + _this.deselectVersion(element); + }); + + this.selectedIds.remove(itemId); + } + + self.highlightAll = function () { + var _this = this; + $j(this.selectedIds.sIds).each( + function (index, id) { + _this.select(id, true); + } + ); + } + + self.bindResult = function () { + var res = this.selectedIds.sIds.join("|"); + $j('#selectedItems', parent.document.body).val(res); + } + + self.switch = function (element) { + var switchOn = !this.isSelected(element); + if (switchOn) { + this.select(element.id); + } else { + this.deselect(element.id); + } + } + + return self; +} + +ASL.initialize = function (selectedIds, indexName) { + if (indexName !== undefined && indexName != null && indexName.length !== 0) { + window.indexName = indexName; + } + + var selectedIdsInst = ASL.initSelectedIds(selectedIds); + + var searchResult = ASL.initSearchResult(selectedIdsInst); + + window.toggleSelected = function (clickableElement) { + var element = searchResult.getItemElement(clickableElement); + if (element == null) { + return; + } + + searchResult.switch(element); + searchResult.bindResult(); + } + + var originalParseResults = window.parseResults; + window.parseResults = function(resultCallBack) { + originalParseResults(resultCallBack); + searchResult.highlightAll(); + } + + // Removes their default implementation + window.BindItemResult = function (b) {} + window.BindItemResultDatasource = function () { } + + window.establishViews = function() { + $j("#views").children().each(function (index, view) { + if (view.id != "list") { + view.hide(); + } + }); + } + + SC.waitFor('SC.libsAreLoaded', function () { ASL.OnLoad(searchResult); }); +} + +ASL.OnLoad = function (searchResult) { + searchResult.bindResult(); +} + +if (ASL.validState()) { + ASL.initialize(window.selectedIds, window.aslIndex); +} + diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASL/AdvancedSearchListControl.cs b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASL/AdvancedSearchListControl.cs new file mode 100644 index 0000000..29654ad --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASL/AdvancedSearchListControl.cs @@ -0,0 +1,388 @@ + +namespace Sitecore.Support.ASL +{ + using System; + using System.Collections.Generic; + using System.Text; + using System.Web.UI; + using Sitecore; + using Sitecore.Annotations; + using Sitecore.ContentSearch.Diagnostics; + using Sitecore.Data; + using Sitecore.Data.Items; + using Sitecore.Diagnostics; + using Sitecore.Globalization; + using Sitecore.Resources; + using Sitecore.SecurityModel; + using Sitecore.Shell.Applications.ContentEditor; + using Sitecore.Support.ASL.Datasource; + using Sitecore.Support.ASL.ParamsStorage; + using Sitecore.Text; + using Sitecore.Web.UI.Sheer; + + /// + /// Custom Control which is used to render view of the field in content editor + /// Uses Search tab in order to get list of selected values. + /// + public class AdvancedSearchListControl : Web.UI.HtmlControls.Control, IContentField + { + protected static IDataSourceReader sourceFieldSource = new MlsDataSource(); + + protected static IDataSourceReader filterFieldSource = new PersistentBucketFilterField(); + + public AdvancedSearchListControl() + { + this.Class = "scContentControlMultilist"; + this.Activation = true; + } + + public static readonly string SelectItemsDialogPath = "/sitecore/shell/default.aspx?xmlcontrol=ASLSelectItems"; + + public static readonly char[] Sep = { '|' }; + + protected override void OnLoad(EventArgs e) + { + Assert.ArgumentNotNull(e, "e"); + base.OnLoad(e); + + // To track changes of Up and Down actions + this.UpdateValueFromClient(); + } + + private void RenderInitJSDoRender(HtmlTextWriter output) + { + output.Write(@""); + } + + private void UpdateValueFromClient() + { + string value = Sitecore.Context.ClientPage.ClientRequest.Form[this.ID + "_Value"]; + if (value != null && this.Value != value) + { + this.SetModified(); + // TODO Validation of the new value + this.Value = value; + } + } + + private void UpdateValueToClient(string newValue) + { + var selectedIds = ExtractSelectedIdsFromString(newValue); + this.SetModified(); + this.Value = newValue; + + var sb = new StringBuilder(); + foreach (var id in selectedIds) + { + this.RenderOption(id, sb); + } + + // TODO improve + Sitecore.Context.ClientPage.ClientResponse.SetAttribute(this.ID + "_Value", "value", newValue); + Sitecore.Context.ClientPage.ClientResponse.SetInnerHtml(this.ID + "_selected", sb.ToString()); + } + + + protected override void DoRender(HtmlTextWriter output) + { + this.RenderInitJSDoRender(output); + + Assert.ArgumentNotNull(output, "output"); + + this.ServerProperties["ID"] = this.ID; + string str = string.Empty; + if (this.ReadOnly) + { + str = " disabled=\"disabled\""; + } + + output.Write(""); + output.Write("
"); + output.Write(""); + output.Write(""); + output.Write(""); + output.Write("" + Images.GetSpacer(20, 1) + ""); + output.Write("" + Translate.Text("Selected") + ""); + output.Write(""); + output.Write(""); + output.Write(""); + this.RenderButton(output, "ASL/16x16/delete.png", "javascript:ASLC.deleteCurrent('" + this.ID + "')"); + output.Write("
"); + this.RenderButton(output, "ASL/16x16/navigate_up.png", "javascript:scContent.multilistMoveUp('" + this.ID + "')"); + output.Write("
"); + this.RenderButton(output, "ASL/16x16/navigate_down.png", "javascript:scContent.multilistMoveDown('" + this.ID + "')"); + output.Write(""); + output.Write(""); + output.Write(""); + output.Write(""); + output.Write(""); + output.Write(""); + output.Write("
"); + } + + private void RenderButton(HtmlTextWriter output, string icon, string click) + { + Assert.ArgumentNotNull(output, "output"); + Assert.ArgumentNotNull(icon, "icon"); + Assert.ArgumentNotNull(click, "click"); + var builder = new ImageBuilder + { + Src = icon, + Class = "scNavButton", + Width = 0x10, + Height = 0x10, + Margin = "2px" + }; + if (!this.ReadOnly) + { + builder.OnClick = click; + } + + output.Write(builder.ToString()); + } + + public static IEnumerable ExtractSelectedIdsFromString(string value) + { + // TODO Remove duplicates + Assert.IsNotNull(value, "value"); + if (String.IsNullOrWhiteSpace(value)) + { + return new List(); + } + + var strIds = value.Split(Sep, StringSplitOptions.RemoveEmptyEntries); + + var ids = new List(strIds.Length); + foreach (var strId in strIds) + { + ID id; + if (!Data.ID.TryParse(strId, out id)) + { + Log.Debug(string.Format("AdvancedSearchListControl: Can't parse ID:'{0}'", strId)); + continue; + } + else + { + ids.Add(id); + } + } + + return ids; + } + + + public override void HandleMessage(Message message) + { + base.HandleMessage(message); + + if (message["id"] != this.ID) + { + return; + } + + switch (message.Name) + { + case "searchlist:opensearch": + // Handle Select Items command to open search dialog + Sitecore.Context.ClientPage.Start(this, "SelectItems"); + return; + + case "searchlist:unselectall": + // Handle the Unselect All command + Sitecore.Context.ClientPage.Start(this, "UnselectAll"); + return; + } + } + + protected void RenderOption(ID id, StringBuilder sb) + { + Item item; + using (new LanguageSwitcher(this.ItemLanguage)) + { + item = Sitecore.Context.ContentDatabase.GetItem(id); + } + + if (item != null) + { + sb.AppendFormat("", item.ID, item.DisplayName); + } + else + { + sb.AppendFormat("", id, Translate.Text("[Item not found]")); + } + } + + protected string RetrieveFilters(out string indexName, out ID rootItemId) + { + var itemIdParsed = Sitecore.Data.ID.Parse(this.ItemID); + var fieldIdParsed = Sitecore.Data.ID.Parse(this.FieldID); + + using (new SecurityDisabler()) + { + // TODO Validate if content db must be used + var db = Sitecore.Context.ContentDatabase; + var item = db.GetItem(itemIdParsed); + var fieldItem = db.GetItem(fieldIdParsed); + + // sourceFieldSource's are ordered in such way because filterFieldSource uses a token which exactly identifies the source + if (filterFieldSource.IsUsed(item, (TemplateFieldItem)fieldItem)) + { + return filterFieldSource.GetDatasource(item, (TemplateFieldItem)fieldItem, out indexName, out rootItemId); + } + + if (sourceFieldSource.IsUsed(item, (TemplateFieldItem)fieldItem)) + { + return sourceFieldSource.GetDatasource(item, (TemplateFieldItem)fieldItem, out indexName, out rootItemId); + } + } + + CrawlingLog.Log.Warn("SLA Can't identify source of parameters for the field"); + indexName = null; + rootItemId = null; + + return null; + } + + [UsedImplicitly] + private void SelectItems(ClientPipelineArgs args) + { + Assert.ArgumentNotNull(args, "args"); + if (args.IsPostBack) + { + if (args.Result != "undefined" && args.Result != null && this.Value != args.Result) + { + this.UpdateValueToClient(args.Result); + } + } + else + { + this.UpdateValueFromClient(); + + string indexName; + ID indexResolveItem; + var filters = this.RetrieveFilters(out indexName, out indexResolveItem); + // TODO LOG DEBUG ASL Control Parameters + + var storage = ASLSpecificStorage.GetStorage(); + storage.Value = this.Value; + storage.FieldId = Sitecore.Data.ID.Parse(this.FieldID); + storage.IndexName = indexName; + storage.IndexResolveItem = indexResolveItem; + storage.Filters = filters; + + var urlString = new UrlString(AdvancedSearchListControl.SelectItemsDialogPath); + storage.InnerStorage.Flush(ref urlString); + + Sitecore.Context.ClientPage.ClientResponse.ShowModalDialog(urlString.ToString(), "1050", "700", string.Empty, true); + args.WaitForPostBack(); + } + } + + [UsedImplicitly] + private void UnselectAll(ClientPipelineArgs args) + { + Assert.ArgumentNotNull(args, "args"); + if (!args.IsPostBack) + { + if (this.Value.Length == 0) + { + return; + } + + this.UpdateValueToClient(string.Empty); + } + } + + public bool ReadOnly { get; set; } + + public string ItemID + { + get + { + return StringUtil.GetString(this.ViewState["ItemID"]); + } + + set + { + Assert.ArgumentNotNull(value, "value"); + this.ViewState["ItemID"] = value; + } + } + + public string ItemLanguage + { + get + { + return StringUtil.GetString(this.ViewState["ItemLanguage"]); + } + + set + { + Assert.ArgumentNotNull(value, "value"); + this.ViewState["ItemLanguage"] = value; + } + } + + public string Source + { + get + { + return StringUtil.GetString(this.GetViewStateProperty("Source", string.Empty)); + } + + set + { + if (value != this.Value) + { + this.SetViewStateProperty("Source", value, string.Empty); + } + } + } + + public string FieldID + { + get + { + return StringUtil.GetString(this.ViewState["FieldID"]); + } + + set + { + Assert.ArgumentNotNull(value, "value"); + this.ViewState["FieldID"] = value; + } + } + + protected void SetModified() + { + Sitecore.Context.ClientPage.Modified = true; + } + + public void SetValue(string value) + { + Assert.ArgumentNotNull(value, "value"); + this.Value = value; + } + + public string GetValue() + { + return this.Value; + } + } +} diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASLSelectItems.aspx b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASLSelectItems.aspx new file mode 100644 index 0000000..01f31bb --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASLSelectItems.aspx @@ -0,0 +1,24 @@ +<%@ Page Language="C#" MasterPageFile="~/sitecore/shell/Applications/Buckets/ItemBucketsSearchResult.Master" AutoEventWireup="true" CodeBehind="ASLSelectItems.aspx.cs" Inherits="Sitecore.Support.ASL.ASLSelectItems" %> + + +<%@ Register TagPrefix="sc" TagName="BucketSearchUI" Src="./BucketSearchUI.ascx" %> +<%@ OutputCache Location="None" VaryByParam="none" %> + +<%--TODO: Disabled javascript include--%> + + + + + + + \ No newline at end of file diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASLSelectItems.aspx.cs b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASLSelectItems.aspx.cs new file mode 100644 index 0000000..84fd3f9 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Buckets/ASLSelectItems.aspx.cs @@ -0,0 +1,154 @@ + +namespace Sitecore.Support.ASL +{ + using System; + using System.Text; + using System.Web; + using System.Web.UI; + using Sitecore.Buckets.Client.sitecore_modules.Shell.Sitecore.Buckets; + using Sitecore.Buckets.Pipelines.UI.ExpandIdBasedSearchFilters; + using Sitecore.ContentSearch.Diagnostics; + using Sitecore.Data; + using Sitecore.Data.Fields; + using Sitecore.Data.Items; + using Sitecore.Diagnostics; + using Sitecore.SecurityModel; + using Sitecore.StringExtensions; + using Sitecore.Support.ASL.ParamsStorage; + using Sitecore.Web; + + public class ASLSelectItems : Sitecore.sitecore.admin.AdminPage + { + private static readonly char[] aslValueSeparator = new char[] {'|'}; + + protected override void OnInit(EventArgs arguments) + { + Assert.ArgumentNotNull(arguments, "arguments"); + if (!Sitecore.Context.User.IsAuthenticated) + { + this.CheckSecurity(true); + } + + base.OnInit(arguments); + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + if (this.IsPostBack) + { + // Required since if Enter is pressed in the dialogue, a post back is sent + return; + } + + var storage = ASLSpecificStorage.GetStorage(); + + var index = this.PrepareIndexParameter(storage.IndexName); + + string defaultFilters, persistFilters; + using (new SecurityDisabler()) + { + var contentDatabase = Sitecore.Context.ContentDatabase; + + persistFilters = storage.Filters; + persistFilters = this.PrepareClauses(contentDatabase, persistFilters); + + defaultFilters = this.GetDefaultClauses(storage.FieldId); + defaultFilters = this.PrepareClauses(contentDatabase, defaultFilters); + } + + var ids = this.PrepareSelectedIds(storage.Value); + + // TODO Check this + storage.InnerStorage.Clear(); + + string content = string.Format("", + defaultFilters, persistFilters, ids, index); + + this.AddToHead(content); + } + + protected void AddToHead(string content) + { + ((ItemBucketsSearchResult)base.Master).DynamicHeadPlaceholder.Controls.Add(new LiteralControl(content)); + } + + protected string ExtractDefaultFilter(Item fieldItem) + { + Assert.ArgumentNotNull(fieldItem, "Field must exist..."); + + var contentDatabase = Sitecore.Context.ContentDatabase; + Assert.IsNotNull(contentDatabase, "Content Database must exist..."); + + Field field = fieldItem.Fields[Sitecore.Buckets.Util.Constants.DefaultQuery]; + if (field == null) + { + return string.Empty; + } + + var args = new ExpandIdBasedSearchFiltersArgs(field.Value, contentDatabase); + ExpandIdBasedSearchFiltersPipeline.Run(args); + return args.ExpandedFilters; + } + + protected string PrepareClauses(Database db, string value) + { + Assert.ArgumentNotNull(db, "db"); + + var args = new ExpandIdBasedSearchFiltersArgs(value, db); + ExpandIdBasedSearchFiltersPipeline.Run(args); + return HttpUtility.JavaScriptStringEncode(args.ExpandedFilters); + } + + protected string GetDefaultClauses(ID fieldId) + { + // TODO Check if content db must be used here + var db = Sitecore.Context.ContentDatabase; + Assert.IsNotNull(db, "Can't get the content database"); + + var fieldItem = db.GetItem(fieldId); + Field field = fieldItem.Fields[Sitecore.Buckets.Util.Constants.DefaultQuery]; + + Assert.IsNotNull(field, "Can't get the DefaultQuery field..."); + return field.Value; + } + + protected string PrepareSelectedIds(string value) + { + if (value.IsNullOrEmpty()) + { + return string.Empty; + } + + var ids = value.Split(aslValueSeparator, StringSplitOptions.RemoveEmptyEntries); + + if (ids.Length == 0) + { + return string.Empty; + } + + var sb = new StringBuilder(43 * ids.Length); + foreach (var id in ids) + { + if (Sitecore.Data.ID.IsID(id)) + { + sb.AppendFormat("\"{0}\",", HttpUtility.JavaScriptStringEncode(id)); + } + else + { + CrawlingLog.Log.Warn(string.Format("ASL Can't parse ID '{0}' from the field value. It will be skipped", id)); + } + } + + // sb.Length - 1 to remove the ending comma + return sb.ToString(0, sb.Length - 1); + } + + protected string PrepareIndexParameter(string value) + { + return value.IsNullOrEmpty() ? string.Empty : string.Format("var aslIndex = '{0}';", HttpUtility.JavaScriptStringEncode(value)); + } + } + +} diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ASLSelectItems.xml b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ASLSelectItems.xml new file mode 100644 index 0000000..b00db76 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ASLSelectItems.xml @@ -0,0 +1,43 @@ + + + + + + + + .scDataSourceResultContainer { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 35px; + background: white; + border: 1px solid #CFCFCF; + margin-bottom: 15px; + } + + .scItemLink { + position:absolute; + bottom:0; + width:100%; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + + + + + + +
+
+ +
+ + + + +
+
+
+
diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ASLSelectItemsForm.cs b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ASLSelectItemsForm.cs new file mode 100644 index 0000000..2852593 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ASLSelectItemsForm.cs @@ -0,0 +1,64 @@ + +namespace Sitecore.Support.ASL +{ + using System; + using Sitecore.Data; + using Sitecore.Diagnostics; + using Sitecore.Support.ASL.ParamsStorage; + using Sitecore.Text; + using Sitecore.Web.UI.HtmlControls; + + /// + /// Logic to show select items dialog with search. Used by + /// + public class ASLSelectItemsForm : Sitecore.Web.UI.Pages.DialogForm + { + protected Edit selectedItems; + + protected Frame search; + + protected override void OnLoad(EventArgs e) + { + Assert.ArgumentNotNull(e, "e"); + + base.OnLoad(e); + + var storage = ASLSpecificStorage.GetStorage(); + var itemId = storage.IndexResolveItem; + + if (itemId != (ID)null) + { + // It is required to pass the id parameter for index resolution through the Referrer header + this.search.SourceUri = this.AppendQueryStringParameter(this.search.SourceUri, "id", itemId.ToString()); + } + } + + protected override void OnOK(object sender, EventArgs args) + { + Assert.ArgumentNotNull(sender, "sender"); + Assert.ArgumentNotNull(args, "args"); + + Context.ClientPage.ClientResponse.SetDialogValue(this.selectedItems.Value); + base.OnOK(sender, args); + } + + private string AppendQueryStringParameter(string urlStr, string key, string value) + { + Assert.ArgumentNotNull(urlStr, "urlStr"); + Assert.ArgumentNotNull(key, "key"); + Assert.ArgumentNotNull(value, "value"); + + var url = new UrlString(urlStr); + var pV = url.Parameters[key]; + + if (pV != null && !string.IsNullOrWhiteSpace(pV)) + { + return url.ToString(); + } + + url[key] = value; + + return url.ToString(); + } + } +} diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/Datasource/IDataSourceReader.cs b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/Datasource/IDataSourceReader.cs new file mode 100644 index 0000000..bb129e2 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/Datasource/IDataSourceReader.cs @@ -0,0 +1,19 @@ +namespace Sitecore.Support.ASL.Datasource +{ + using Sitecore.Data; + using Sitecore.Data.Items; + + public interface IDataSourceReader + { + /* Result examples: + text:panda;custom:_name|home + +custom:_name|home;template:{76036f5e-cbce-46d1-af0a-4143f9b557aa} + sort:_name sort:_name[desc] + +custom:_name|home;-custom:_name|home;custom:_name|home + */ + + string GetDatasource(Item dataItem, TemplateFieldItem fieldItem, out string indexName, out ID rootItemID); + + bool IsUsed(Item dataItem, TemplateFieldItem fieldItem); + } +} diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/Datasource/MlsDataSource.cs b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/Datasource/MlsDataSource.cs new file mode 100644 index 0000000..4216e82 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/Datasource/MlsDataSource.cs @@ -0,0 +1,293 @@ + +namespace Sitecore.Support.ASL.Datasource +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Linq; + using System.Web; + using Sitecore.ContentSearch.Diagnostics; + using Sitecore.ContentSearch.Utilities; + using Sitecore.Data; + using Sitecore.Data.Items; + using Sitecore.Diagnostics; + + public class MlsDataSource : IDataSourceReader + { + const string SortAsc = "asc"; + const string SortDesc = "desc"; + const string FastQueryPrefix = "fast:"; + const string QueryPrefix = "query:"; + + readonly static string[] Separator = new[] { "|" }; + readonly static string[] SubSeparator = new[] { ":" }; + + public string GetDatasource(Item dataItem, TemplateFieldItem fieldItem, out string indexName, out ID rootItemId) + { + Assert.ArgumentNotNull(dataItem, "dataItem"); + Assert.ArgumentNotNull(fieldItem, "fieldName"); + + var dsParams = this.BuildNameValueCollection(fieldItem.Source); + + var filters = this.ExtractFilters(dsParams, q => dataItem.Axes.SelectSingleItem(q), out rootItemId, out indexName); + + return this.ComposeString(filters); + } + + public bool IsUsed(Item dataItem, TemplateFieldItem fieldItem) + { + var value = fieldItem.Source; + return !string.IsNullOrWhiteSpace(value) && value.Contains("="); + } + + protected virtual string ComposeString(List filters) + { + Assert.ArgumentNotNull(filters, "filters"); + return string.Join(";", filters); + } + + protected virtual NameValueCollection BuildNameValueCollection(string source) + { + Assert.ArgumentNotNull(source, "source"); + return StringUtil.GetNameValues(source, '=', '&'); + } + + protected virtual List ExtractFilters(NameValueCollection values, Func resolveItemByQuery, out ID location, out string indexName) + { + // Supported parameters: + /* + StartSearchLocation + Language + SortField + IndexName + TemplateFilter + FullTextQuery + TemplateFilter + Filter + */ + + Assert.ArgumentNotNull(values, "values"); + + location = null; + indexName = null; + + var parsedParams = new List(); + + foreach (string str in values.AllKeys) + { + values[str] = HttpUtility.JavaScriptStringEncode(values[str]); + } + + // Parse StartSearchLocation + string locationParam = values["StartSearchLocation"]; + if (!string.IsNullOrWhiteSpace(locationParam)) + { + location = this.PrepareLocationFilter(resolveItemByQuery, locationParam); + parsedParams.Add(new SearchStringModel("location", location.ToString(), "must")); + } + + // Parse FullTextQuery + string fullTextPapam = values["FullTextQuery"]; + if (!string.IsNullOrWhiteSpace(fullTextPapam)) + { + // TODO Check if ecaping is required + parsedParams.Add(new SearchStringModel("text", fullTextPapam.Trim(), "must")); + } + + // Parse TemplateFilter + string templatesParam = values["TemplateFilter"]; + if (!string.IsNullOrWhiteSpace(templatesParam)) + { + var templateIDs = templatesParam.Split(Separator, StringSplitOptions.RemoveEmptyEntries); + var validtemplateIDs = templateIDs.Where(ID.IsID).ToArray(); + if (validtemplateIDs.Length > 0) + { + foreach (var validtemplateID in validtemplateIDs) + { + parsedParams.Add(new SearchStringModel("template", validtemplateID, "should")); + } + } + + if (templateIDs.Length != validtemplateIDs.Length) + { + CrawlingLog.Log.Warn(string.Format("ASL Some specified IDs are incorrect '{0}'... Parameter will be skipped", templatesParam)); + } + } + + // Parse Language + string languageParam = values["Language"]; + if (!string.IsNullOrWhiteSpace(languageParam)) + { + parsedParams.Add(new SearchStringModel("language", languageParam.Trim(), "must")); + } + + // Parse Sort + string sortParam = values["SortField"]; + bool isAsc; + string fieldName; + if (!string.IsNullOrWhiteSpace(sortParam) && this.TryParseSortParam(sortParam, out isAsc, out fieldName)) + { + parsedParams.Add(new SearchStringModel("sort", fieldName, isAsc ? SortAsc : SortDesc)); + } + + // Parse Filter + string filterParam = values["Filter"]; + if (filterParam != null) + { + this.ParseAndAppendFilterValues(filterParam, parsedParams); + } + + // Parse IndexName + indexName = values["IndexName"]; + if (!string.IsNullOrWhiteSpace(indexName)) + { + indexName = indexName.Trim().ToLower(); + location = null; + } + + return parsedParams; + } + + protected void ParseAndAppendFilterValues(string filterValue, List parsedParams) + { + Assert.ArgumentNotNull(filterValue, "filterValue"); + Assert.ArgumentNotNull(parsedParams, "parsedParams"); + + if (filterValue.Length == 0) + { + return; + } + + var clauses = filterValue.Split(Separator, StringSplitOptions.RemoveEmptyEntries); + + foreach (string clause in clauses) + { + var clauseParts = clause.Split(SubSeparator, StringSplitOptions.RemoveEmptyEntries); + if (clauseParts.Length != 2) + { + CrawlingLog.Log.Warn(string.Format("ASL Can't parse clause '{0}' of Filter parameter", clause)); + continue; + } + + var value = clauseParts[1].Trim(); + if (value.Length < 1) + { + value = null; + } + + var opAndField = clauseParts[0].Trim(); + var field = (opAndField[0] == '-' || opAndField[0] == '+') ? opAndField.Substring(1) : opAndField; + field = field.Trim(); + + var operation = (opAndField[0] == '+') ? "must" : opAndField[0] == '-' ? "not" : "should"; + + parsedParams.Add(new SearchStringModel("custom", string.Format("{0}|{1}", field, value), operation)); + } + } + + protected bool TryParseSortParam(string sortValue, out bool isAsc, out string fieldName) + { + // _name[desc] + sortValue = sortValue.Trim(); + var startIndex = sortValue.IndexOf("[", StringComparison.InvariantCulture); + var endIndex = sortValue.IndexOf("]", StringComparison.InvariantCulture); + + + if (startIndex == 0) + { + Log.Warn(string.Format("ASL Can't find a field name... Value:'{0}'", sortValue), this); + isAsc = true; + fieldName = null; + return false; + } + + if (startIndex * endIndex < 0 || endIndex <= startIndex) + { + Log.Warn(string.Format("ASL Sort order has an invalid format... Value:'{0}'", sortValue), this); + isAsc = true; + fieldName = null; + return false; + } + + // Parse field name + // TODO Check this + fieldName = sortValue.Substring(0, startIndex).Trim(); + if (string.IsNullOrEmpty(fieldName)) + { + Log.Warn(string.Format("ASL Can't find a field name... Value:'{0}'", sortValue), this); + isAsc = true; + fieldName = null; + return false; + } + + // Parse sort order + // Handles both cases brackets are empty or not present + if (endIndex == startIndex) + { + isAsc = true; + } + else + { + bool? asc = null; + + // TODO Check this + var orderStr = sortValue.Substring(startIndex + 1, endIndex - startIndex - 1).Trim(); + if (orderStr.Equals(SortAsc, StringComparison.InvariantCultureIgnoreCase)) + { + asc = true; + } + + if (orderStr.Equals(SortDesc, StringComparison.InvariantCultureIgnoreCase)) + { + asc = false; + } + + if (asc == null) + { + Log.Warn(string.Format("ASL sort direction is an invalid value... Value:'{0}'", sortValue), this); + asc = true; + } + + if (endIndex != sortValue.Length - 1) + { + Log.Warn(string.Format("ASL Sort order has an invalid format... Value:'{0}'", sortValue), this); + } + + isAsc = asc.Value; + } + + return true; + } + + protected ID PrepareLocationFilter(Func resolveItemByQuery, string locationFilter) + { + if (string.IsNullOrWhiteSpace(locationFilter)) + { + return ItemIDs.RootID; + } + + ID id; + if (ID.TryParse(locationFilter, out id)) + { + return id; + } + + if (locationFilter.StartsWith(QueryPrefix)) + { + locationFilter = locationFilter.Replace("->", "="); + string query = locationFilter.Substring(QueryPrefix.Length); + if (query.StartsWith(FastQueryPrefix)) + { + Log.Warn("ASL The field doesn't support fast queries. Sitecore query will be used instead...", this); + query = query.Substring(FastQueryPrefix.Length); + } + + var resItem = resolveItemByQuery(query); + return resItem.ID; + } + + Log.Warn("ASL The field can't parse location filter. Root item will be used intead.", this); + return ItemIDs.RootID; + } + } +} diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/Datasource/PersistentBucketFilterField.cs b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/Datasource/PersistentBucketFilterField.cs new file mode 100644 index 0000000..b5078fb --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/Datasource/PersistentBucketFilterField.cs @@ -0,0 +1,72 @@ + +namespace Sitecore.Support.ASL.Datasource +{ + using Sitecore.Data; + using Sitecore.Data.Items; + using Sitecore.Diagnostics; + using Sitecore.StringExtensions; + + public class PersistentBucketFilterField : IDataSourceReader + { + public const string SourcePrefix = "UseFilterFieldAsSource"; + + public string GetDatasource(Item dataItem, TemplateFieldItem fieldItem, out string indexName, out ID rootItemId) + { + Assert.ArgumentNotNull(dataItem, "dataItem"); + Assert.ArgumentNotNull(fieldItem, "fieldName"); + + this.ParseSourceValue(fieldItem.Source, out indexName, out rootItemId); + + return this.GetFilterFieldValue(fieldItem); + } + + public bool IsUsed(Item dataItem, TemplateFieldItem fieldItem) + { + Assert.ArgumentNotNull(dataItem, "dataItem"); + Assert.ArgumentNotNull(fieldItem, "fieldName"); + var rawSource = fieldItem.Source; + + if (string.IsNullOrWhiteSpace(rawSource)) + { + return false; + } + + return rawSource.TrimStart().StartsWith(SourcePrefix); + } + + protected virtual void ParseSourceValue(string rawValue, out string indexName, out ID rootItemId) + { + indexName = null; + rootItemId = null; + + rawValue = rawValue.Trim(); + + //SourcePrefix.Length + 1 since ':' must be set after the prefix + if (rawValue.IsNullOrEmpty() || rawValue.Length <= PersistentBucketFilterField.SourcePrefix.Length + 1) + { + return; + } + + var value = rawValue.Substring(PersistentBucketFilterField.SourcePrefix.Length + 1).TrimStart(); + + if (value.IsNullOrEmpty()) + { + return; + } + + if (ID.TryParse(value, out rootItemId)) + { + return; + } + + indexName = value.ToLowerInvariant(); + rootItemId = null; + } + + private string GetFilterFieldValue(TemplateFieldItem fieldItem) + { + var field = fieldItem.InnerItem.Fields[Sitecore.Buckets.Util.Constants.DefaultFilter]; + return string.IsNullOrWhiteSpace(field.Value) ? null : field.Value; + } + } +} diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ParamsStorage/ASLSpecificStorage.cs b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ParamsStorage/ASLSpecificStorage.cs new file mode 100644 index 0000000..ce55833 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ParamsStorage/ASLSpecificStorage.cs @@ -0,0 +1,90 @@ + +namespace Sitecore.Support.ASL.ParamsStorage +{ + using Sitecore.Data; + + public class ASLSpecificStorage : AbstractWrappedStorage + { + public static readonly string ValueParam = "va"; + public static readonly string FieldIdParam = "fld"; + public static readonly string IndexNameParam = "idx"; + public static readonly string IndexResolveItemParam = "rid"; + public static readonly string FiltersParam = "flt"; + + public static ASLSpecificStorage GetStorage() + { + return UrlHandleStorage.GetWrappedStorage(v => new ASLSpecificStorage(v)); + } + + protected ASLSpecificStorage(UrlHandleStorage innerStorage) : base(innerStorage) + { + } + + public string Value + { + get + { + return this.InnerStorage[ValueParam]; + } + + set + { + this.InnerStorage[ValueParam] = value ?? string.Empty; + } + } + + public ID FieldId + { + get + { + var strId = this.InnerStorage[FieldIdParam]; + return string.IsNullOrWhiteSpace(strId) ? null : ID.Parse(strId); + } + + set + { + this.InnerStorage[FieldIdParam] = value == (ID)null ? string.Empty : value.ToString(); + } + } + + public ID IndexResolveItem + { + get + { + var strId = this.InnerStorage[IndexResolveItemParam]; + return string.IsNullOrWhiteSpace(strId) ? null : ID.Parse(strId); + } + + set + { + this.InnerStorage[IndexResolveItemParam] = value == (ID)null ? string.Empty : value.ToString(); + } + } + + public string IndexName + { + get + { + return this.InnerStorage[IndexNameParam]; + } + + set + { + this.InnerStorage[IndexNameParam] = value ?? string.Empty; + } + } + + public string Filters + { + get + { + return this.InnerStorage[FiltersParam]; + } + + set + { + this.InnerStorage[FiltersParam] = value ?? string.Empty; + } + } + } +} diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ParamsStorage/AbstractWrappedStorage.cs b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ParamsStorage/AbstractWrappedStorage.cs new file mode 100644 index 0000000..68a8296 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ParamsStorage/AbstractWrappedStorage.cs @@ -0,0 +1,16 @@ + +namespace Sitecore.Support.ASL.ParamsStorage +{ + using Sitecore.Diagnostics; + + public abstract class AbstractWrappedStorage + { + protected AbstractWrappedStorage(UrlHandleStorage innerStorage) + { + Assert.ArgumentNotNull(innerStorage, "innerStorage"); + this.InnerStorage = innerStorage; + } + + public UrlHandleStorage InnerStorage { get; protected set; } + } +} diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ParamsStorage/UrlHandleStorage.cs b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ParamsStorage/UrlHandleStorage.cs new file mode 100644 index 0000000..08120ab --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Applications/Dialogs/ASL/ParamsStorage/UrlHandleStorage.cs @@ -0,0 +1,110 @@ + +namespace Sitecore.Support.ASL.ParamsStorage +{ + using System; + using System.Collections.Specialized; + using System.Reflection; + using System.Web; + using Sitecore.Diagnostics; + using Sitecore.Text; + using Sitecore.Web; + + public class UrlHandleStorage + { + public static readonly string HandleName = "aslhdl"; + + private UrlHandle handle; + + protected UrlHandleStorage(UrlHandle handle) + { + Assert.IsNotNull(handle, "handle"); + this.handle = handle; + } + + public void Flush(ref UrlString url) + { + Assert.ArgumentNotNull(url, "urlString"); + // To workaround issue 423453: + // this.handle.Add(url, UrlHandleStorage.HandleName); + this.handle.AddFixed(url, UrlHandleStorage.HandleName); + } + + public void Clear() + { + UrlHandle.DisposeHandle(this.handle); + } + + public string this[string key] + { + get + { + return this.handle[key]; + } + + set + { + this.handle[key] = value; + } + } + + public static T GetWrappedStorage(Func createMethod) where T : AbstractWrappedStorage + { + return createMethod(UrlHandleStorage.GetStorage()); + } + + public static UrlHandleStorage GetStorage() + { + var handleId = ExtractHandleId(HttpContext.Current.Request); + + if (handleId == null) + { + return new UrlHandleStorage(new UrlHandle()); + } + + var qParam = new NameValueCollection + { + { UrlHandleStorage.HandleName, handleId } + }; + + var urlHandle = UrlHandle.Get(new UrlString(qParam), UrlHandleStorage.HandleName); + + return new UrlHandleStorage(urlHandle); + } + + private static string ExtractHandleId(HttpRequest r) + { + Assert.ArgumentNotNull(r, "r"); + + var handleValue = WebUtil.ExtractUrlParm(UrlHandleStorage.HandleName, r.Url.Query); + if (string.IsNullOrWhiteSpace(handleValue) && r.UrlReferrer != null) + { + handleValue = WebUtil.ExtractUrlParm(UrlHandleStorage.HandleName, r.UrlReferrer.Query); + } + + return string.IsNullOrWhiteSpace(handleValue) ? null : handleValue; + } + } + + // To workaround issue 423453 in the line: + internal static class Issue423453 + { + private static readonly FieldInfo fiValues; + + static Issue423453() + { + fiValues = typeof(Sitecore.Web.UrlHandle).GetField("_values", BindingFlags.Instance | BindingFlags.NonPublic); + } + + public static void AddFixed(this UrlHandle handle, UrlString urlString, string handleName) + { + Assert.ArgumentNotNull(handle, "handle"); + Assert.ArgumentNotNull(urlString, "urlString"); + Assert.ArgumentNotNullOrEmpty(handleName, "handleName"); + + var values = fiValues.GetValue(handle) as NameValueCollection; + var str = new UrlString(values); + urlString[handleName] = handle.Handle; + WebUtil.SetSessionValue(handle.Handle, str.ToString()); + } + } +} diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/16x16/delete.png b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/16x16/delete.png new file mode 100644 index 0000000..c18d3f6 Binary files /dev/null and b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/16x16/delete.png differ diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/16x16/navigate_down.png b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/16x16/navigate_down.png new file mode 100644 index 0000000..fe981c3 Binary files /dev/null and b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/16x16/navigate_down.png differ diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/16x16/navigate_up.png b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/16x16/navigate_up.png new file mode 100644 index 0000000..0b2717c Binary files /dev/null and b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/16x16/navigate_up.png differ diff --git a/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/FixFor72.css b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/FixFor72.css new file mode 100644 index 0000000..7535977 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/sitecore/shell/Themes/Standard/ASL/FixFor72.css @@ -0,0 +1,12 @@ +.scFormTable > tbody > tr > td { + padding: 5px; +} + +.scFormTable > tbody > tr > td:first-child { + width: 1px; + white-space: nowrap; +} + +input:not([type]) { + width: 100%; +} \ No newline at end of file diff --git a/src/Sitecore.Support.AdvancedSearchList/web.config b/src/Sitecore.Support.AdvancedSearchList/web.config new file mode 100644 index 0000000..ab0a990 --- /dev/null +++ b/src/Sitecore.Support.AdvancedSearchList/web.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file