From 866c63cc6311a5603dbf9625dc9b4f850106ad23 Mon Sep 17 00:00:00 2001 From: Sven Wappler Date: Sat, 17 Apr 2021 00:26:33 +0200 Subject: [PATCH] first commit --- .editorconfig | 30 + .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/bug_report.md | 35 + .github/ISSUE_TEMPLATE/feature_request.md | 23 + .github/ISSUE_TEMPLATE/task.md | 10 + .github/no-response.yml | 14 + .gitignore | 18 + Classes/AbstractDataHandlerListener.php | 217 + Classes/Access/Rootline.php | 228 + Classes/Access/RootlineElement.php | 187 + .../Access/RootlineElementFormatException.php | 34 + Classes/AdditionalFieldsIndexer.php | 126 + Classes/AdditionalPageIndexer.php | 49 + Classes/Api.php | 58 + .../IndexingConfigurationSelectorField.php | 213 + Classes/Backend/SiteSelectorField.php | 72 + Classes/ConnectionManager.php | 267 + Classes/ContentObject/Classification.php | 117 + Classes/ContentObject/Content.php | 80 + Classes/ContentObject/Multivalue.php | 92 + Classes/ContentObject/Relation.php | 385 + Classes/Controller/AbstractBaseController.php | 276 + .../Controller/Backend/PageModuleSummary.php | 214 + .../Search/AbstractModuleController.php | 328 + .../CoreOptimizationModuleController.php | 386 + .../IndexAdministrationModuleController.php | 195 + .../Search/IndexQueueModuleController.php | 287 + .../Backend/Search/InfoModuleController.php | 324 + Classes/Controller/SearchController.php | 210 + Classes/Controller/SuggestController.php | 81 + .../Index/Classification/Classification.php | 111 + .../Classification/ClassificationService.php | 88 + Classes/Domain/Index/IndexService.php | 318 + .../Helper/UriBuilder/AbstractUriStrategy.php | 159 + .../Helper/UriBuilder/TYPO3SiteStrategy.php | 85 + .../PageIndexer/Helper/UriStrategyFactory.php | 61 + .../Queue/GarbageRemover/AbstractStrategy.php | 175 + .../Queue/GarbageRemover/PageStrategy.php | 80 + .../Queue/GarbageRemover/RecordStrategy.php | 43 + .../Queue/GarbageRemover/StrategyFactory.php | 42 + .../IndexQueueIndexingPropertyRepository.php | 86 + .../Queue/QueueInitializationService.php | 162 + .../Index/Queue/QueueItemRepository.php | 902 ++ .../ConfigurationAwareRecordService.php | 186 + .../Helper/MountPagesUpdater.php | 112 + .../RecordMonitor/Helper/RootPageResolver.php | 315 + .../Index/Queue/Statistic/QueueStatistic.php | 138 + .../Statistic/QueueStatisticsRepository.php | 108 + .../Search/ApacheSolrDocument/Builder.php | 312 + .../Search/ApacheSolrDocument/Repository.php | 170 + .../FrequentSearchesService.php | 179 + .../Highlight/SiteHighlighterUrlModifier.php | 60 + .../LastSearches/LastSearchesRepository.php | 156 + .../LastSearches/LastSearchesService.php | 148 + .../LastSearchesWriterProcessor.php | 74 + .../Search/Query/AbstractQueryBuilder.php | 558 + .../Domain/Search/Query/ExtractingQuery.php | 47 + .../Search/Query/Helper/EscapeService.php | 147 + .../AbstractDeactivatable.php | 46 + .../ParameterBuilder/AbstractFieldList.php | 115 + .../ParameterBuilder/BigramPhraseFields.php | 95 + .../Query/ParameterBuilder/Elevation.php | 139 + .../Query/ParameterBuilder/Faceting.php | 282 + .../ParameterBuilder/FieldCollapsing.php | 159 + .../Search/Query/ParameterBuilder/Filters.php | 158 + .../Query/ParameterBuilder/Grouping.php | 254 + .../Query/ParameterBuilder/Highlighting.php | 211 + .../Query/ParameterBuilder/Operator.php | 106 + .../ParameterBuilder/ParameterBuilder.php | 44 + .../Query/ParameterBuilder/PhraseFields.php | 95 + .../Query/ParameterBuilder/QueryFields.php | 117 + .../Query/ParameterBuilder/ReturnFields.php | 128 + .../Search/Query/ParameterBuilder/Slops.php | 257 + .../Search/Query/ParameterBuilder/Sorting.php | 112 + .../Query/ParameterBuilder/Sortings.php | 99 + .../Query/ParameterBuilder/Spellchecking.php | 102 + .../ParameterBuilder/TrigramPhraseFields.php | 94 + Classes/Domain/Search/Query/Query.php | 47 + Classes/Domain/Search/Query/QueryBuilder.php | 587 + Classes/Domain/Search/Query/SearchQuery.php | 27 + Classes/Domain/Search/Query/SuggestQuery.php | 86 + .../Search/ResultSet/Facets/AbstractFacet.php | 290 + .../ResultSet/Facets/AbstractFacetItem.php | 116 + .../Facets/AbstractFacetItemCollection.php | 102 + .../ResultSet/Facets/AbstractFacetPackage.php | 92 + .../ResultSet/Facets/AbstractFacetParser.php | 189 + .../Facets/DefaultFacetQueryBuilder.php | 65 + .../ResultSet/Facets/DefaultUrlDecoder.php | 31 + .../ResultSet/Facets/FacetCollection.php | 78 + .../ResultSet/Facets/FacetParserInterface.php | 34 + .../Facets/FacetQueryBuilderInterface.php | 41 + .../Search/ResultSet/Facets/FacetRegistry.php | 108 + .../Facets/FacetUrlDecoderInterface.php | 45 + .../Facets/InvalidFacetPackageException.php | 27 + .../Facets/InvalidFacetParserException.php | 27 + .../Facets/InvalidQueryBuilderException.php | 27 + .../Facets/InvalidUrlDecoderException.php | 27 + .../OptionBased/AbstractOptionFacetItem.php | 69 + .../OptionBased/AbstractOptionsFacet.php | 92 + .../OptionBased/Hierarchy/HierarchyFacet.php | 130 + .../Hierarchy/HierarchyFacetParser.php | 159 + .../Hierarchy/HierarchyPackage.php | 37 + .../OptionBased/Hierarchy/HierarchyTool.php | 41 + .../Hierarchy/HierarchyUrlDecoder.php | 64 + .../Facets/OptionBased/Hierarchy/Node.php | 119 + .../OptionBased/Hierarchy/NodeCollection.php | 44 + .../Facets/OptionBased/OptionCollection.php | 75 + .../Facets/OptionBased/Options/Option.php | 39 + .../OptionBased/Options/OptionsFacet.php | 49 + .../Options/OptionsFacetParser.php | 148 + .../Options/OptionsFacetQueryBuilder.php | 147 + .../OptionBased/Options/OptionsPackage.php | 37 + .../Facets/OptionBased/QueryGroup/Option.php | 37 + .../QueryGroup/QueryGroupFacet.php | 49 + .../QueryGroup/QueryGroupFacetParser.php | 143 + .../QueryGroupFacetQueryBuilder.php | 38 + .../QueryGroup/QueryGroupPackage.php | 46 + .../QueryGroup/QueryGroupUrlDecoder.php | 50 + .../RangeBased/AbstractRangeFacetItem.php | 74 + .../RangeBased/AbstractRangeFacetParser.php | 107 + .../Facets/RangeBased/DateRange/DateRange.php | 124 + .../DateRange/DateRangeCollection.php | 45 + .../RangeBased/DateRange/DateRangeCount.php | 64 + .../RangeBased/DateRange/DateRangeFacet.php | 93 + .../DateRange/DateRangeFacetParser.php | 85 + .../DateRange/DateRangeFacetQueryBuilder.php | 58 + .../RangeBased/DateRange/DateRangePackage.php | 46 + .../DateRange/DateRangeUrlDecoder.php | 74 + .../RangeBased/NumericRange/NumericRange.php | 123 + .../NumericRange/NumericRangeCollection.php | 44 + .../NumericRange/NumericRangeCount.php | 63 + .../NumericRange/NumericRangeFacet.php | 93 + .../NumericRange/NumericRangeFacetParser.php | 77 + .../NumericRangeFacetQueryBuilder.php | 44 + .../NumericRange/NumericRangePackage.php | 46 + .../NumericRange/NumericRangeUrlDecoder.php | 69 + .../RenderingInstructions/FormatDate.php | 74 + .../ResultSet/Facets/RequirementsService.php | 124 + .../ResultSet/Facets/SortingExpression.php | 71 + .../Search/ResultSet/Grouping/Group.php | 122 + .../ResultSet/Grouping/GroupCollection.php | 88 + .../Search/ResultSet/Grouping/GroupItem.php | 156 + .../Grouping/GroupItemCollection.php | 33 + .../Result/Parser/AbstractResultParser.php | 72 + .../Result/Parser/DefaultResultParser.php | 88 + .../Result/Parser/DocumentEscapeService.php | 98 + .../Result/Parser/ResultParserRegistry.php | 145 + .../Search/ResultSet/Result/SearchResult.php | 242 + .../ResultSet/Result/SearchResultBuilder.php | 66 + .../Result/SearchResultCollection.php | 74 + .../ResultSetReconstitutionProcessor.php | 264 + .../Search/ResultSet/SearchResultSet.php | 445 + .../ResultSet/SearchResultSetProcessor.php | 44 + .../ResultSet/SearchResultSetService.php | 499 + .../Search/ResultSet/Sorting/Sorting.php | 169 + .../ResultSet/Sorting/SortingCollection.php | 68 + .../ResultSet/Sorting/SortingHelper.php | 82 + .../ResultSet/Spellchecking/Suggestion.php | 106 + Classes/Domain/Search/Score/Score.php | 100 + .../Search/Score/ScoreCalculationService.php | 134 + Classes/Domain/Search/SearchRequest.php | 737 ++ Classes/Domain/Search/SearchRequestAware.php | 44 + .../Domain/Search/SearchRequestBuilder.php | 189 + .../Statistics/StatisticsRepository.php | 225 + .../Statistics/StatisticsWriterProcessor.php | 228 + .../Domain/Search/Suggest/SuggestService.php | 276 + .../Domain/Search/Uri/SearchUriBuilder.php | 399 + ...dSiteConfigurationCombinationException.php | 29 + Classes/Domain/Site/Site.php | 266 + Classes/Domain/Site/SiteHashService.php | 141 + Classes/Domain/Site/SiteInterface.php | 128 + Classes/Domain/Site/SiteRepository.php | 341 + Classes/Domain/Site/Typo3ManagedSite.php | 100 + Classes/Domain/Variants/IdBuilder.php | 99 + Classes/Domain/Variants/IdModifier.php | 45 + Classes/Domain/Variants/VariantsProcessor.php | 125 + Classes/Eid/Api.php | 52 + Classes/Eid/SiteHash.php | 63 + .../AbstractHierarchyProcessor.php | 62 + .../FieldProcessor/CategoryUidToHierarchy.php | 128 + Classes/FieldProcessor/FieldProcessor.php | 45 + Classes/FieldProcessor/PageUidToHierarchy.php | 125 + Classes/FieldProcessor/PathToHierarchy.php | 80 + Classes/FieldProcessor/Service.php | 114 + Classes/FieldProcessor/TimestampToIsoDate.php | 58 + .../FieldProcessor/TimestampToUtcIsoDate.php | 58 + Classes/FrontendEnvironment.php | 126 + Classes/FrontendEnvironment/Tsfe.php | 159 + Classes/FrontendEnvironment/TypoScript.php | 132 + Classes/GarbageCollector.php | 380 + Classes/GarbageCollectorPostProcessor.php | 46 + Classes/HtmlContentExtractor.php | 257 + Classes/IndexQueue/AbstractIndexer.php | 336 + .../AdditionalIndexQueueItemIndexer.php | 49 + .../DocumentPreparationException.php | 33 + .../Exception/IndexingException.php | 34 + .../FrontendHelper/AbstractFrontendHelper.php | 123 + .../FrontendHelper/AuthorizationService.php | 126 + .../IndexQueue/FrontendHelper/Dispatcher.php | 89 + .../FrontendHelper/FrontendHelper.php | 70 + Classes/IndexQueue/FrontendHelper/Manager.php | 98 + .../PageFieldMappingIndexer.php | 164 + .../IndexQueue/FrontendHelper/PageIndexer.php | 364 + .../FrontendHelper/UserGroupDetector.php | 253 + Classes/IndexQueue/Indexer.php | 721 ++ .../InitializationPostProcessor.php | 52 + .../Initializer/AbstractInitializer.php | 405 + .../Initializer/IndexQueueInitializer.php | 75 + Classes/IndexQueue/Initializer/Page.php | 342 + Classes/IndexQueue/Initializer/Record.php | 40 + .../IndexQueue/InvalidFieldNameException.php | 35 + Classes/IndexQueue/Item.php | 528 + Classes/IndexQueue/NoPidException.php | 35 + Classes/IndexQueue/PageIndexer.php | 401 + .../IndexQueue/PageIndexerDataUrlModifier.php | 47 + .../PageIndexerDocumentsModifier.php | 48 + Classes/IndexQueue/PageIndexerRequest.php | 449 + .../IndexQueue/PageIndexerRequestHandler.php | 118 + Classes/IndexQueue/PageIndexerResponse.php | 158 + Classes/IndexQueue/Queue.php | 610 + Classes/IndexQueue/RecordMonitor.php | 601 + .../IndexQueue/SerializedValueDetector.php | 51 + Classes/LanguageFileUnavailableException.php | 34 + .../Middleware/FrontendUserAuthenticator.php | 156 + Classes/Middleware/PageIndexerFinisher.php | 73 + .../Middleware/PageIndexerInitialization.php | 80 + Classes/Migrations/Migration.php | 45 + .../Migrations/RemoveSiteFromScheduler.php | 106 + .../Mvc/Controller/SolrControllerContext.php | 71 + Classes/NoSolrConnectionFoundException.php | 69 + Classes/PageDocumentPostProcessor.php | 51 + Classes/PingFailedException.php | 33 + Classes/Query/Modifier/Elevation.php | 64 + Classes/Query/Modifier/Faceting.php | 265 + Classes/Query/Modifier/Modifier.php | 48 + Classes/Query/Modifier/Statistics.php | 62 + Classes/Report/AbstractSolrStatus.php | 74 + .../AccessFilterPluginInstalledStatus.php | 189 + Classes/Report/AllowUrlFOpenStatus.php | 69 + Classes/Report/FilterVarStatus.php | 68 + Classes/Report/SchemaStatus.php | 101 + Classes/Report/SiteHandlingStatus.php | 189 + Classes/Report/SolrConfigStatus.php | 84 + Classes/Report/SolrConfigurationStatus.php | 235 + Classes/Report/SolrStatus.php | 263 + Classes/Report/SolrVersionStatus.php | 138 + .../Response/Processor/ResponseProcessor.php | 52 + Classes/Search.php | 291 + Classes/Search/AbstractComponent.php | 51 + Classes/Search/AccessComponent.php | 82 + Classes/Search/AnalysisComponent.php | 81 + Classes/Search/DebugComponent.php | 102 + Classes/Search/ElevationComponent.php | 45 + Classes/Search/FacetingComponent.php | 47 + Classes/Search/FacetsModifier.php | 46 + Classes/Search/HighlightingComponent.php | 77 + Classes/Search/LastSearchesComponent.php | 47 + Classes/Search/QueryAware.php | 46 + Classes/Search/RelevanceComponent.php | 88 + Classes/Search/ResponseModifier.php | 47 + Classes/Search/SearchAware.php | 47 + Classes/Search/SearchComponent.php | 50 + Classes/Search/SearchComponentManager.php | 116 + Classes/Search/SortingComponent.php | 135 + Classes/Search/SpellcheckingComponent.php | 83 + Classes/Search/StatisticsComponent.php | 72 + Classes/SubstitutePageIndexer.php | 48 + Classes/System/Cache/TwoLevelCache.php | 138 + .../Configuration/ConfigurationManager.php | 114 + .../ConfigurationPageResolver.php | 117 + .../Configuration/ExtensionConfiguration.php | 130 + .../Configuration/TypoScriptConfiguration.php | 2251 ++++ .../ContentObject/ContentObjectService.php | 84 + Classes/System/Data/AbstractCollection.php | 169 + Classes/System/Data/DateTime.php | 41 + Classes/System/DateTime/FormatService.php | 128 + Classes/System/Environment/CliEnvironment.php | 106 + .../WebRootAllReadyDefinedException.php | 35 + .../Backend/Toolbar/ClearCacheActionsHook.php | 82 + .../Language/FrontendOverlayService.php | 144 + Classes/System/Logging/DebugWriter.php | 99 + Classes/System/Logging/SolrLogManager.php | 98 + .../InvalidViewObjectNameException.php | 31 + Classes/System/Mvc/Backend/ModuleData.php | 84 + .../Service/ModuleDataStorageService.php | 89 + .../System/Object/AbstractClassRegistry.php | 113 + Classes/System/Page/Rootline.php | 125 + Classes/System/Records/AbstractRepository.php | 130 + .../System/Records/Pages/PagesRepository.php | 296 + .../SystemCategoryRepository.php | 51 + .../SystemLanguageRepository.php | 91 + .../SystemTemplateRepository.php | 67 + .../System/Service/ConfigurationService.php | 142 + .../System/Session/FrontendUserSession.php | 93 + Classes/System/Solr/Document/Document.php | 52 + Classes/System/Solr/Node.php | 166 + Classes/System/Solr/Parser/SchemaParser.php | 103 + Classes/System/Solr/Parser/StopWordParser.php | 74 + Classes/System/Solr/Parser/SynonymParser.php | 75 + Classes/System/Solr/ParsingUtil.php | 34 + Classes/System/Solr/RequestFactory.php | 44 + Classes/System/Solr/ResponseAdapter.php | 150 + Classes/System/Solr/Schema/Schema.php | 77 + .../Solr/Service/AbstractSolrService.php | 451 + .../System/Solr/Service/SolrAdminService.php | 390 + .../System/Solr/Service/SolrReadService.php | 120 + .../System/Solr/Service/SolrWriteService.php | 121 + .../Solr/SolrCommunicationException.php | 52 + Classes/System/Solr/SolrConnection.php | 311 + .../Solr/SolrIncompleteResponseException.php | 31 + .../Solr/SolrInternalServerErrorException.php | 31 + .../System/Solr/SolrUnavailableException.php | 31 + Classes/System/TCA/TCAService.php | 315 + Classes/System/Url/UrlHelper.php | 193 + .../UserFunctions/FlexFormUserFunctions.php | 222 + Classes/System/Util/ArrayAccessor.php | 257 + Classes/System/Util/SiteUtility.php | 185 + Classes/System/Validator/Path.php | 51 + Classes/Task/AbstractMeilisearchTask.php | 100 + Classes/Task/IndexQueueWorkerTask.php | 209 + ...QueueWorkerTaskAdditionalFieldProvider.php | 180 + Classes/Task/ReIndexTask.php | 149 + .../ReIndexTaskAdditionalFieldProvider.php | 255 + Classes/Typo3PageContentExtractor.php | 175 + Classes/Typo3PageIndexer.php | 501 + Classes/Util.php | 172 + Classes/Utility/ManagedResourcesUtility.php | 106 + ...AbstractSolrFrontendTagBasedViewHelper.php | 68 + .../AbstractSolrFrontendViewHelper.php | 80 + .../AbstractSolrTagBasedViewHelper.php | 42 + .../ViewHelpers/AbstractSolrViewHelper.php | 42 + .../Backend/Button/HelpButtonViewHelper.php | 69 + .../Backend/IsStringViewHelper.php | 57 + .../IfHasAccessToModuleViewHelper.php | 136 + .../Debug/DocumentScoreAnalyzerViewHelper.php | 97 + Classes/ViewHelpers/Debug/QueryViewHelper.php | 54 + .../Document/HighlightResultViewHelper.php | 110 + .../Document/RelevanceViewHelper.php | 72 + .../Facet/Area/GroupViewHelper.php | 67 + .../Group/Prefix/LabelFilterViewHelper.php | 76 + .../Group/Prefix/LabelPrefixesViewHelper.php | 102 + .../ViewHelpers/Format/ArrayViewHelper.php | 38 + .../PageBrowserRangeViewHelper.php | 75 + Classes/ViewHelpers/SearchFormViewHelper.php | 257 + Classes/ViewHelpers/TranslateViewHelper.php | 94 + .../ViewHelpers/Uri/AbstractUriViewHelper.php | 79 + .../Uri/Facet/AbstractValueViewHelper.php | 102 + .../Uri/Facet/AddFacetItemViewHelper.php | 45 + .../Uri/Facet/RemoveAllFacetsViewHelper.php | 41 + .../Uri/Facet/RemoveFacetItemViewHelper.php | 46 + .../Uri/Facet/RemoveFacetViewHelper.php | 53 + .../Uri/Facet/SetFacetItemViewHelper.php | 46 + .../Uri/Paginate/GroupItemPageViewHelper.php | 55 + .../Uri/Paginate/ResultPageViewHelper.php | 52 + .../Result/AddSearchWordListViewHelper.php | 70 + .../Uri/Search/CurrentSearchViewHelper.php | 41 + .../Uri/Search/StartNewSearchViewHelper.php | 51 + .../Uri/Sorting/RemoveSortingViewHelper.php | 41 + .../Uri/Sorting/SetSortingViewHelper.php | 53 + .../AbstractPaginateWidgetController.php | 136 + .../FrequentlySearchedController.php | 114 + .../GroupItemPaginateController.php | 95 + .../Controller/LastSearchesController.php | 41 + .../Controller/ResultPaginateController.php | 84 + .../Widget/FrequentlySearchedViewHelper.php | 50 + .../Widget/GroupItemPaginateViewHelper.php | 71 + .../Widget/LastSearchesViewHelper.php | 50 + .../Widget/ResultPaginateViewHelper.php | 69 + Classes/Widget/AbstractWidgetController.php | 115 + Classes/Widget/AbstractWidgetViewHelper.php | 247 + Classes/Widget/WidgetRequest.php | 38 + Configuration/FlexForms/Form.xml | 57 + Configuration/FlexForms/Results.xml | 194 + Configuration/RequestMiddlewares.php | 20 + .../SiteConfiguration/Overrides/sites.php | 171 + Configuration/TCA/Overrides/sys_template.php | 75 + Configuration/TCA/Overrides/tt_content.php | 48 + .../TSconfig/ContentElementWizard.typoscript | 2 + .../Mod/Wizards/NewContentElement.tsconfig | 13 + .../TypoScript/BootstrapCss/setup.txt | 2 + .../TypoScript/BootstrapCss/setup.typoscript | 9 + .../TypoScript/Examples/Ajaxify/setup.txt | 2 + .../Examples/Ajaxify/setup.typoscript | 39 + .../Examples/BoostQueries/setup.txt | 2 + .../Examples/BoostQueries/setup.typoscript | 5 + .../Examples/ConnectionFromConfVars/setup.txt | 2 + .../ConnectionFromConfVars/setup.typoscript | 24 + .../Examples/EverythingOn/setup.txt | 2 + .../Examples/EverythingOn/setup.typoscript | 56 + .../Examples/Facets/DateRange/setup.txt | 2 + .../Facets/DateRange/setup.typoscript | 28 + .../Examples/Facets/Hierarchy/setup.txt | 2 + .../Facets/Hierarchy/setup.typoscript | 23 + .../Examples/Facets/NumericRange/setup.txt | 2 + .../Facets/NumericRange/setup.typoscript | 25 + .../Examples/Facets/Options/setup.txt | 2 + .../Examples/Facets/Options/setup.typoscript | 12 + .../Examples/Facets/OptionsFiltered/setup.txt | 2 + .../Facets/OptionsFiltered/setup.typoscript | 13 + .../Facets/OptionsPrefixGrouped/setup.txt | 2 + .../OptionsPrefixGrouped/setup.typoscript | 13 + .../Facets/OptionsSinglemode/setup.txt | 2 + .../Facets/OptionsSinglemode/setup.typoscript | 14 + .../Examples/Facets/OptionsToggle/setup.txt | 2 + .../Facets/OptionsToggle/setup.typoscript | 13 + .../Examples/Facets/QueryGroup/setup.txt | 2 + .../Facets/QueryGroup/setup.typoscript | 56 + .../TypoScript/Examples/FilterPages/setup.txt | 2 + .../Examples/FilterPages/setup.typoscript | 7 + .../Examples/IndexQueueNews/setup.txt | 2 + .../Examples/IndexQueueNews/setup.typoscript | 97 + .../IndexQueueNewsContentElements/setup.txt | 2 + .../setup.typoscript | 17 + .../Examples/IndexQueueTtNews/setup.txt | 2 + .../IndexQueueTtNews/setup.typoscript | 67 + .../TypoScript/Examples/Suggest/setup.txt | 2 + .../Examples/Suggest/setup.typoscript | 48 + .../TypoScript/OpenSearch/constants.txt | 2 + .../OpenSearch/constants.typoscript | 10 + Configuration/TypoScript/OpenSearch/setup.txt | 2 + .../TypoScript/OpenSearch/setup.typoscript | 104 + Configuration/TypoScript/Solr/constants.txt | 2 + .../TypoScript/Solr/constants.typoscript | 27 + Configuration/TypoScript/Solr/setup.txt | 2 + .../TypoScript/Solr/setup.typoscript | 429 + .../TypoScript/StyleSheets/setup.txt | 2 + .../TypoScript/StyleSheets/setup.typoscript | 4 + LICENSE.txt | 674 + PULL_REQUEST_TEMPLATE.md | 9 + README.md | 67 + .../indexingconfigurationselectorfield.css | 22 + Resources/Css/JQueryUi/jquery-ui.custom.css | 433 + Resources/Css/ModAdmin/index.css | 34 + Resources/Css/Report/index.css | 29 + .../Images/JQueryUi/ui-anim_basic_16x16.gif | Bin 0 -> 1553 bytes .../JQueryUi/ui-bg_glass_55_fcf0ba_1x400.png | Bin 0 -> 131 bytes .../ui-bg_gloss-wave_100_ece8da_500x100.png | Bin 0 -> 2031 bytes .../ui-bg_highlight-hard_100_f7f7f7_1x100.png | Bin 0 -> 85 bytes .../ui-bg_highlight-hard_100_fafaf4_1x100.png | Bin 0 -> 97 bytes .../ui-bg_highlight-hard_15_f18f0b_1x100.png | Bin 0 -> 125 bytes .../ui-bg_highlight-hard_95_cccccc_1x100.png | Bin 0 -> 105 bytes .../ui-bg_highlight-soft_25_f18f0b_1x100.png | Bin 0 -> 122 bytes .../ui-bg_highlight-soft_95_ffedad_1x100.png | Bin 0 -> 130 bytes .../ui-bg_inset-soft_15_2b2922_1x100.png | Bin 0 -> 119 bytes .../JQueryUi/ui-icons_808080_256x240.png | Bin 0 -> 4369 bytes .../JQueryUi/ui-icons_847e71_256x240.png | Bin 0 -> 4369 bytes .../JQueryUi/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4369 bytes .../JQueryUi/ui-icons_d9a55e_256x240.png | Bin 0 -> 5355 bytes .../JQueryUi/ui-icons_e3a345_256x240.png | Bin 0 -> 4369 bytes .../JQueryUi/ui-icons_eeeeee_256x240.png | Bin 0 -> 4369 bytes .../JQueryUi/ui-icons_ffffff_256x240.png | Bin 0 -> 4369 bytes Resources/Private/.htaccess | 2 + Resources/Private/Install/.htaccess | 2 + Resources/Private/Install/install-solr.sh | 260 + Resources/Private/Language/cn.locallang.xlf | 133 + Resources/Private/Language/de.locallang.xlf | 218 + Resources/Private/Language/dk.locallang.xlf | 165 + Resources/Private/Language/fr.locallang.xlf | 222 + Resources/Private/Language/it.locallang.xlf | 134 + Resources/Private/Language/jp.locallang.xlf | 133 + Resources/Private/Language/kr.locallang.xlf | 133 + Resources/Private/Language/locallang.xlf | 361 + Resources/Private/Language/locallang_mod.xlf | 17 + .../Language/locallang_mod_coreoptimize.xlf | 17 + .../Language/locallang_mod_indexadmin.xlf | 17 + .../Language/locallang_mod_indexqueue.xlf | 17 + .../Private/Language/locallang_mod_info.xlf | 17 + Resources/Private/Language/nl.locallang.xlf | 195 + Resources/Private/Language/pl.locallang.xlf | 133 + .../Private/Layouts/Backend/WithPageTree.html | 10 + Resources/Private/Layouts/Facet.html | 1 + Resources/Private/Layouts/Fullwidth.html | 8 + Resources/Private/Layouts/Split.html | 7 + .../ApacheSolr/FieldTypesForSingleCore.html | 56 + .../Backend/ApacheSolr/SingleDocument.html | 51 + .../Partials/Backend/FlashMessages.html | 1 + .../Partials/Backend/NoSiteAvailable.html | 34 + .../Private/Partials/Facets/Default.html | 9 + .../Private/Partials/Facets/Hierarchy.html | 30 + .../Private/Partials/Facets/Options.html | 32 + .../Partials/Facets/OptionsFiltered.html | 40 + .../Partials/Facets/OptionsPrefixGrouped.html | 50 + .../Partials/Facets/OptionsSinglemode.html | 29 + .../Partials/Facets/OptionsToggle.html | 41 + .../Private/Partials/Facets/RangeDate.html | 41 + .../Private/Partials/Facets/RangeNumeric.html | 36 + .../Private/Partials/Facets/Rootline.html | 30 + .../Private/Partials/Result/Document.html | 48 + Resources/Private/Partials/Result/Facets.html | 22 + .../Private/Partials/Result/FacetsActive.html | 23 + .../Private/Partials/Result/PerPage.html | 19 + .../Private/Partials/Result/RelevanceBar.html | 18 + .../Private/Partials/Result/Sorting.html | 48 + Resources/Private/Partials/Search/Form.html | 32 + .../Partials/Search/FrequentlySearched.html | 34 + .../Private/Partials/Search/LastSearches.html | 35 + .../Php/ComposerLibraries/composer.json | 12 + .../Php/ComposerLibraries/composer.lock | 417 + Resources/Private/Php/strptime/license.pdf | Bin 0 -> 28902 bytes Resources/Private/Php/strptime/strptime.php | 170 + .../_schema_analysis_stopwords_arabic.json | 127 + .../_schema_analysis_stopwords_armenian.json | 53 + .../_schema_analysis_stopwords_basque.json | 106 + ...alysis_stopwords_brazilian_portuguese.json | 136 + .../_schema_analysis_stopwords_bulgarian.json | 198 + .../_schema_analysis_stopwords_burmese.json | 9 + .../_schema_analysis_stopwords_catalan.json | 227 + .../_schema_analysis_stopwords_chinese.json | 9 + .../_schema_analysis_stopwords_czech.json | 180 + .../_schema_analysis_stopwords_danish.json | 102 + .../_schema_analysis_stopwords_dutch.json | 109 + .../_schema_analysis_stopwords_english.json | 182 + .../_schema_analysis_stopwords_finnish.json | 243 + .../_schema_analysis_stopwords_french.json | 171 + .../_schema_analysis_stopwords_galician.json | 168 + .../_schema_analysis_stopwords_generic.json | 9 + .../_schema_analysis_stopwords_german.json | 239 + .../_schema_analysis_stopwords_greek.json | 83 + .../_schema_analysis_stopwords_hindi.json | 235 + .../_schema_analysis_stopwords_hungarian.json | 207 + ..._schema_analysis_stopwords_indonesian.json | 365 + .../_schema_analysis_stopwords_irish.json | 182 + .../_schema_analysis_stopwords_italian.json | 287 + .../_schema_analysis_stopwords_japanese.json | 9 + .../_schema_analysis_stopwords_khmer.json | 9 + .../_schema_analysis_stopwords_korean.json | 9 + .../conf/_schema_analysis_stopwords_lao.json | 9 + .../_schema_analysis_stopwords_latvia.json | 8 + .../_schema_analysis_stopwords_norwegian.json | 184 + .../_schema_analysis_stopwords_persian.json | 316 + .../_schema_analysis_stopwords_polish.json | 282 + ..._schema_analysis_stopwords_portuguese.json | 211 + .../_schema_analysis_stopwords_romanian.json | 238 + .../_schema_analysis_stopwords_russian.json | 167 + .../_schema_analysis_stopwords_serbian.json | 9 + .../_schema_analysis_stopwords_spanish.json | 316 + .../_schema_analysis_stopwords_swedish.json | 122 + .../conf/_schema_analysis_stopwords_thai.json | 9 + .../_schema_analysis_stopwords_turkish.json | 217 + .../_schema_analysis_stopwords_ukrainian.json | 167 + .../_schema_analysis_synonyms_arabic.json | 1 + .../_schema_analysis_synonyms_armenian.json | 1 + .../_schema_analysis_synonyms_basque.json | 1 + ...nalysis_synonyms_brazilian_portuguese.json | 1 + .../_schema_analysis_synonyms_bulgarian.json | 1 + .../_schema_analysis_synonyms_burmese.json | 1 + .../_schema_analysis_synonyms_catalan.json | 1 + .../_schema_analysis_synonyms_chinese.json | 1 + .../conf/_schema_analysis_synonyms_czech.json | 1 + .../_schema_analysis_synonyms_danish.json | 1 + .../conf/_schema_analysis_synonyms_dutch.json | 1 + .../_schema_analysis_synonyms_english.json | 1 + .../_schema_analysis_synonyms_finnish.json | 1 + .../_schema_analysis_synonyms_french.json | 1 + .../_schema_analysis_synonyms_galician.json | 1 + .../_schema_analysis_synonyms_generic.json | 1 + .../_schema_analysis_synonyms_german.json | 1 + .../conf/_schema_analysis_synonyms_greek.json | 1 + .../conf/_schema_analysis_synonyms_hindi.json | 1 + .../_schema_analysis_synonyms_hungarian.json | 1 + .../_schema_analysis_synonyms_indonesian.json | 1 + .../conf/_schema_analysis_synonyms_irish.json | 1 + .../_schema_analysis_synonyms_italian.json | 1 + .../_schema_analysis_synonyms_japanese.json | 1 + .../conf/_schema_analysis_synonyms_khmer.json | 1 + .../_schema_analysis_synonyms_korean.json | 1 + .../conf/_schema_analysis_synonyms_lao.json | 1 + .../_schema_analysis_synonyms_latvia.json | 1 + .../_schema_analysis_synonyms_norwegian.json | 1 + .../_schema_analysis_synonyms_persian.json | 1 + .../_schema_analysis_synonyms_polish.json | 1 + .../_schema_analysis_synonyms_portuguese.json | 1 + .../_schema_analysis_synonyms_romanian.json | 1 + .../_schema_analysis_synonyms_russian.json | 1 + .../_schema_analysis_synonyms_serbian.json | 1 + .../_schema_analysis_synonyms_spanish.json | 1 + .../_schema_analysis_synonyms_swedish.json | 1 + .../conf/_schema_analysis_synonyms_thai.json | 1 + .../_schema_analysis_synonyms_turkish.json | 1 + .../_schema_analysis_synonyms_ukrainian.json | 1 + .../ext_solr_11_0_0/conf/admin-extra.html | 14 + .../ext_solr_11_0_0/conf/arabic/protwords.txt | 1 + .../ext_solr_11_0_0/conf/arabic/schema.xml | 205 + .../conf/armenian/protwords.txt | 1 + .../ext_solr_11_0_0/conf/armenian/schema.xml | 172 + .../ext_solr_11_0_0/conf/basque/protwords.txt | 1 + .../ext_solr_11_0_0/conf/basque/schema.xml | 187 + .../conf/brazilian_portuguese/protwords.txt | 1 + .../conf/brazilian_portuguese/schema.xml | 190 + .../conf/bulgarian/protwords.txt | 1 + .../ext_solr_11_0_0/conf/bulgarian/schema.xml | 192 + .../conf/burmese/protwords.txt | 1 + .../ext_solr_11_0_0/conf/burmese/readme.txt | 4 + .../ext_solr_11_0_0/conf/burmese/schema.xml | 108 + .../conf/catalan/protwords.txt | 1 + .../ext_solr_11_0_0/conf/catalan/schema.xml | 185 + .../conf/chinese/protwords.txt | 1 + .../ext_solr_11_0_0/conf/chinese/schema.xml | 119 + .../ext_solr_11_0_0/conf/currency.xml | 67 + .../ext_solr_11_0_0/conf/czech/protwords.txt | 1 + .../ext_solr_11_0_0/conf/czech/schema.xml | 187 + .../ext_solr_11_0_0/conf/danish/protwords.txt | 17 + .../ext_solr_11_0_0/conf/danish/schema.xml | 188 + .../ext_solr_11_0_0/conf/dutch/protwords.txt | 1 + .../ext_solr_11_0_0/conf/dutch/schema.xml | 188 + .../ext_solr_11_0_0/conf/elevate.xml | 36 + .../conf/english/protwords.txt | 1 + .../ext_solr_11_0_0/conf/english/schema.xml | 189 + .../conf/finnish/protwords.txt | 1 + .../ext_solr_11_0_0/conf/finnish/schema.xml | 188 + .../ext_solr_11_0_0/conf/french/protwords.txt | 1 + .../ext_solr_11_0_0/conf/french/schema.xml | 189 + .../conf/galician/protwords.txt | 1 + .../ext_solr_11_0_0/conf/galician/schema.xml | 192 + .../conf/general_schema_fields.xml | 273 + .../conf/general_schema_types.xml | 212 + .../conf/generic/protwords.txt | 1 + .../ext_solr_11_0_0/conf/generic/schema.xml | 183 + .../conf/german/german-common-nouns.txt | 3870 ++++++ .../ext_solr_11_0_0/conf/german/protwords.txt | 1 + .../ext_solr_11_0_0/conf/german/schema.xml | 219 + .../ext_solr_11_0_0/conf/greek/protwords.txt | 1 + .../ext_solr_11_0_0/conf/greek/schema.xml | 191 + .../ext_solr_11_0_0/conf/hindi/protwords.txt | 1 + .../ext_solr_11_0_0/conf/hindi/schema.xml | 200 + .../conf/hungarian/protwords.txt | 1 + .../ext_solr_11_0_0/conf/hungarian/schema.xml | 184 + .../conf/indonesian/protwords.txt | 1 + .../conf/indonesian/schema.xml | 190 + .../ext_solr_11_0_0/conf/irish/protwords.txt | 1 + .../ext_solr_11_0_0/conf/irish/schema.xml | 186 + .../conf/italian/protwords.txt | 1 + .../ext_solr_11_0_0/conf/italian/schema.xml | 188 + .../conf/japanese/protwords.txt | 1 + .../ext_solr_11_0_0/conf/japanese/schema.xml | 120 + .../ext_solr_11_0_0/conf/khmer/protwords.txt | 1 + .../ext_solr_11_0_0/conf/khmer/readme.txt | 4 + .../ext_solr_11_0_0/conf/khmer/schema.xml | 102 + .../ext_solr_11_0_0/conf/korean/protwords.txt | 1 + .../ext_solr_11_0_0/conf/korean/schema.xml | 120 + .../ext_solr_11_0_0/conf/lao/protwords.txt | 1 + .../ext_solr_11_0_0/conf/lao/readme.txt | 4 + .../ext_solr_11_0_0/conf/lao/schema.xml | 102 + .../ext_solr_11_0_0/conf/latvia/protwords.txt | 1 + .../ext_solr_11_0_0/conf/latvia/schema.xml | 186 + .../conf/norwegian/protwords.txt | 1 + .../ext_solr_11_0_0/conf/norwegian/schema.xml | 185 + .../conf/persian/protwords.txt | 1 + .../ext_solr_11_0_0/conf/persian/schema.xml | 195 + .../ext_solr_11_0_0/conf/polish/protwords.txt | 17 + .../ext_solr_11_0_0/conf/polish/schema.xml | 191 + .../conf/portuguese/protwords.txt | 1 + .../conf/portuguese/schema.xml | 184 + .../conf/romanian/protwords.txt | 1 + .../ext_solr_11_0_0/conf/romanian/schema.xml | 182 + .../conf/russian/protwords.txt | 1 + .../ext_solr_11_0_0/conf/russian/schema.xml | 187 + .../conf/serbian/protwords.txt | 1 + .../ext_solr_11_0_0/conf/serbian/schema.xml | 190 + .../ext_solr_11_0_0/conf/solrconfig.xml | 524 + .../conf/spanish/protwords.txt | 1 + .../ext_solr_11_0_0/conf/spanish/schema.xml | 189 + .../conf/swedish/protwords.txt | 1 + .../ext_solr_11_0_0/conf/swedish/schema.xml | 187 + .../ext_solr_11_0_0/conf/thai/protwords.txt | 1 + .../ext_solr_11_0_0/conf/thai/schema.xml | 152 + .../conf/turkish/protwords.txt | 1 + .../ext_solr_11_0_0/conf/turkish/schema.xml | 184 + .../conf/ukrainian/protwords.txt | 1 + .../ext_solr_11_0_0/conf/ukrainian/schema.xml | 186 + .../conf/velocity/VM_global_library.vm | 161 + .../ext_solr_11_0_0/conf/velocity/browse.vm | 45 + .../ext_solr_11_0_0/conf/velocity/doc.vm | 39 + .../conf/velocity/facet_fields.vm | 12 + .../ext_solr_11_0_0/conf/velocity/facets.vm | 1 + .../ext_solr_11_0_0/conf/velocity/footer.vm | 16 + .../ext_solr_11_0_0/conf/velocity/head.vm | 45 + .../ext_solr_11_0_0/conf/velocity/header.vm | 3 + .../ext_solr_11_0_0/conf/velocity/hit.vm | 5 + .../conf/velocity/hitGrouped.vm | 18 + .../conf/velocity/jquery.autocomplete.css | 48 + .../conf/velocity/jquery.autocomplete.js | 762 ++ .../ext_solr_11_0_0/conf/velocity/layout.vm | 17 + .../ext_solr_11_0_0/conf/velocity/main.css | 182 + .../ext_solr_11_0_0/conf/velocity/query.vm | 54 + .../ext_solr_11_0_0/conf/velocity/suggest.vm | 3 + .../typo3lib/solr-typo3-plugin-4.0.0.jar | Bin 0 -> 251630 bytes .../Private/Solr/cores/arabic/core.properties | 4 + .../Solr/cores/armenian/core.properties | 4 + .../Private/Solr/cores/basque/core.properties | 4 + .../brazilian_portuguese/core.properties | 4 + .../Solr/cores/bulgarian/core.properties | 4 + .../Solr/cores/burmese/core.properties | 4 + .../Solr/cores/catalan/core.properties | 4 + .../Solr/cores/chinese/core.properties | 4 + .../Private/Solr/cores/czech/core.properties | 4 + .../Private/Solr/cores/danish/core.properties | 4 + .../Private/Solr/cores/dutch/core.properties | 4 + .../Solr/cores/english/core.properties | 4 + .../Solr/cores/finnish/core.properties | 4 + .../Private/Solr/cores/french/core.properties | 4 + .../Solr/cores/galician/core.properties | 4 + .../Private/Solr/cores/german/core.properties | 4 + .../Private/Solr/cores/greek/core.properties | 4 + .../Private/Solr/cores/hindi/core.properties | 4 + .../Solr/cores/hungarian/core.properties | 4 + .../Solr/cores/indonesian/core.properties | 4 + .../Private/Solr/cores/irish/core.properties | 4 + .../Solr/cores/italian/core.properties | 4 + .../Solr/cores/japanese/core.properties | 4 + .../Private/Solr/cores/khmer/core.properties | 4 + .../Private/Solr/cores/korean/core.properties | 4 + .../Private/Solr/cores/lao/core.properties | 4 + .../Private/Solr/cores/latvia/core.properties | 4 + .../Solr/cores/norwegian/core.properties | 4 + .../Solr/cores/persian/core.properties | 4 + .../Private/Solr/cores/polish/core.properties | 4 + .../Solr/cores/portuguese/core.properties | 4 + .../Solr/cores/romanian/core.properties | 4 + .../Solr/cores/russian/core.properties | 4 + .../Solr/cores/serbian/core.properties | 4 + .../Solr/cores/spanish/core.properties | 4 + .../Solr/cores/swedish/core.properties | 4 + .../Private/Solr/cores/thai/core.properties | 4 + .../Solr/cores/turkish/core.properties | 4 + .../Solr/cores/ukrainian/core.properties | 4 + Resources/Private/Solr/solr.xml | 15 + Resources/Private/Solr/zoo.cfg | 31 + .../Templates/Backend/PageModule/Summary.html | 14 + ...FilterPluginInstalledStatusIsOutDated.html | 35 + ...lterPluginInstalledStatusNotInstalled.html | 38 + .../Backend/Reports/RootPageFlagStatus.html | 1 + .../Backend/Reports/SchemaStatus.html | 37 + .../Backend/Reports/SiteHandlingStatus.html | 7 + .../Backend/Reports/SolrConfigStatus.html | 36 + .../SolrConfigurationStatusDomainRecord.html | 8 + .../SolrConfigurationStatusIndexing.html | 8 + .../Templates/Backend/Reports/SolrStatus.html | 47 + .../Backend/Reports/SolrVersionStatus.html | 22 + .../Search/CoreOptimizationModule/Index.html | 183 + .../IndexAdministrationModule/Index.html | 72 + .../Search/IndexQueueModule/Index.html | 154 + .../Search/IndexQueueModule/ShowError.html | 13 + .../Search/InfoModule/DocumentsDetails.html | 8 + .../Backend/Search/InfoModule/Index.html | 334 + .../Private/Templates/Search/Detail.html | 18 + Resources/Private/Templates/Search/Form.html | 9 + .../Templates/Search/FrequentlySearched.html | 11 + .../Private/Templates/Search/Results.html | 124 + .../Templates/Search/SolrNotAvailable.html | 9 + .../Widget/FrequentlySearched/Index.html | 1 + .../Widget/GroupItemPaginate/Index.html | 81 + .../Widget/LastSearches/Index.html | 1 + .../Widget/ResultPaginate/Index.html | 81 + .../glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../glyphicons-halflings-regular.svg | 288 + .../glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes Resources/Public/Icons/Extension.png | Bin 0 -> 4315 bytes .../Public/Images/Icons/ContentElement.svg | 10 + .../Images/Icons/InitSolrConnection.svg | 16 + .../Images/Icons/InitSolrConnections.svg | 17 + .../Images/Icons/ModuleCoreOptimization.svg | 67 + .../Icons/ModuleIndexAdministration.svg | 102 + .../Public/Images/Icons/ModuleIndexQueue.svg | 95 + Resources/Public/Images/Icons/ModuleInfo.svg | 79 + .../Public/Images/Icons/ModuleSolrMain.svg | 96 + Resources/Public/Images/Icons/Search.png | Bin 0 -> 580 bytes Resources/Public/Images/IndicatorDown.png | Bin 0 -> 132 bytes Resources/Public/Images/IndicatorUp.png | Bin 0 -> 129 bytes Resources/Public/Images/SearchButton.gif | Bin 0 -> 263 bytes Resources/Public/Images/dkd_logo.png | Bin 0 -> 11144 bytes .../Public/JavaScript/Bootstrap/bootstrap.js | 2377 ++++ .../JavaScript/Bootstrap/bootstrap.min.js | 7 + Resources/Public/JavaScript/Bootstrap/npm.js | 13 + Resources/Public/JavaScript/Chart.js | 10397 ++++++++++++++++ Resources/Public/JavaScript/FormModal.js | 34 + Resources/Public/JavaScript/JQuery/URI.min.js | 79 + .../Public/JavaScript/JQuery/jquery-ui.min.js | 8 + .../JavaScript/JQuery/jquery.URI.min.js | 7 + .../JQuery/jquery.autocomplete.min.js | 8 + .../Public/JavaScript/JQuery/jquery.min.js | 4 + .../JQuery/ui-i18n/jquery.ui.datepicker-de.js | 23 + .../JQuery/ui-i18n/jquery.ui.datepicker-fr.js | 23 + .../JQuery/ui-i18n/jquery.ui.datepicker-nl.js | 23 + .../Public/JavaScript/SearchStatistics.js | 66 + .../JavaScript/facet_daterange_controller.js | 41 + .../facet_numericrange_controller.js | 63 + .../JavaScript/facet_options_controller.js | 54 + .../Public/JavaScript/search_controller.js | 54 + .../Public/JavaScript/suggest_controller.js | 147 + .../StyleSheets/Backend/IndexQueueModule.css | 35 + .../IndexingConfigurationSelectorField.css | 26 + .../Frontend/Bootstrap/bootstrap-theme.css | 587 + .../Bootstrap/bootstrap-theme.css.map | 1 + .../Bootstrap/bootstrap-theme.min.css | 6 + .../Bootstrap/bootstrap-theme.min.css.map | 1 + .../Frontend/Bootstrap/bootstrap.css | 6757 ++++++++++ .../Frontend/Bootstrap/bootstrap.css.map | 1 + .../Frontend/Bootstrap/bootstrap.min.css | 6 + .../Frontend/Bootstrap/bootstrap.min.css.map | 1 + .../Public/StyleSheets/Frontend/loader.css | 10 + .../Public/StyleSheets/Frontend/results.css | 118 + .../Public/StyleSheets/Frontend/suggest.css | 6 + .../StyleSheets/ModuleAdministration.css | 202 + class.ext_update.php | 106 + composer.json | 79 + ext_conf_template.txt | 11 + ext_emconf.php | 30 + ext_localconf.php | 256 + ext_tables.php | 150 + ext_tables.sql | 129 + 813 files changed, 100696 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/task.md create mode 100644 .github/no-response.yml create mode 100644 .gitignore create mode 100644 Classes/AbstractDataHandlerListener.php create mode 100644 Classes/Access/Rootline.php create mode 100644 Classes/Access/RootlineElement.php create mode 100644 Classes/Access/RootlineElementFormatException.php create mode 100644 Classes/AdditionalFieldsIndexer.php create mode 100644 Classes/AdditionalPageIndexer.php create mode 100644 Classes/Api.php create mode 100644 Classes/Backend/IndexingConfigurationSelectorField.php create mode 100644 Classes/Backend/SiteSelectorField.php create mode 100644 Classes/ConnectionManager.php create mode 100644 Classes/ContentObject/Classification.php create mode 100644 Classes/ContentObject/Content.php create mode 100644 Classes/ContentObject/Multivalue.php create mode 100644 Classes/ContentObject/Relation.php create mode 100644 Classes/Controller/AbstractBaseController.php create mode 100644 Classes/Controller/Backend/PageModuleSummary.php create mode 100644 Classes/Controller/Backend/Search/AbstractModuleController.php create mode 100644 Classes/Controller/Backend/Search/CoreOptimizationModuleController.php create mode 100644 Classes/Controller/Backend/Search/IndexAdministrationModuleController.php create mode 100644 Classes/Controller/Backend/Search/IndexQueueModuleController.php create mode 100644 Classes/Controller/Backend/Search/InfoModuleController.php create mode 100644 Classes/Controller/SearchController.php create mode 100644 Classes/Controller/SuggestController.php create mode 100644 Classes/Domain/Index/Classification/Classification.php create mode 100644 Classes/Domain/Index/Classification/ClassificationService.php create mode 100644 Classes/Domain/Index/IndexService.php create mode 100644 Classes/Domain/Index/PageIndexer/Helper/UriBuilder/AbstractUriStrategy.php create mode 100644 Classes/Domain/Index/PageIndexer/Helper/UriBuilder/TYPO3SiteStrategy.php create mode 100644 Classes/Domain/Index/PageIndexer/Helper/UriStrategyFactory.php create mode 100644 Classes/Domain/Index/Queue/GarbageRemover/AbstractStrategy.php create mode 100644 Classes/Domain/Index/Queue/GarbageRemover/PageStrategy.php create mode 100644 Classes/Domain/Index/Queue/GarbageRemover/RecordStrategy.php create mode 100644 Classes/Domain/Index/Queue/GarbageRemover/StrategyFactory.php create mode 100644 Classes/Domain/Index/Queue/IndexQueueIndexingPropertyRepository.php create mode 100644 Classes/Domain/Index/Queue/QueueInitializationService.php create mode 100644 Classes/Domain/Index/Queue/QueueItemRepository.php create mode 100644 Classes/Domain/Index/Queue/RecordMonitor/Helper/ConfigurationAwareRecordService.php create mode 100644 Classes/Domain/Index/Queue/RecordMonitor/Helper/MountPagesUpdater.php create mode 100644 Classes/Domain/Index/Queue/RecordMonitor/Helper/RootPageResolver.php create mode 100644 Classes/Domain/Index/Queue/Statistic/QueueStatistic.php create mode 100644 Classes/Domain/Index/Queue/Statistic/QueueStatisticsRepository.php create mode 100644 Classes/Domain/Search/ApacheSolrDocument/Builder.php create mode 100644 Classes/Domain/Search/ApacheSolrDocument/Repository.php create mode 100644 Classes/Domain/Search/FrequentSearches/FrequentSearchesService.php create mode 100644 Classes/Domain/Search/Highlight/SiteHighlighterUrlModifier.php create mode 100644 Classes/Domain/Search/LastSearches/LastSearchesRepository.php create mode 100644 Classes/Domain/Search/LastSearches/LastSearchesService.php create mode 100644 Classes/Domain/Search/LastSearches/LastSearchesWriterProcessor.php create mode 100644 Classes/Domain/Search/Query/AbstractQueryBuilder.php create mode 100644 Classes/Domain/Search/Query/ExtractingQuery.php create mode 100644 Classes/Domain/Search/Query/Helper/EscapeService.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/AbstractDeactivatable.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/AbstractFieldList.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/BigramPhraseFields.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/Elevation.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/Faceting.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/FieldCollapsing.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/Filters.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/Grouping.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/Highlighting.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/Operator.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/ParameterBuilder.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/PhraseFields.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/QueryFields.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/ReturnFields.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/Slops.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/Sorting.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/Sortings.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/Spellchecking.php create mode 100644 Classes/Domain/Search/Query/ParameterBuilder/TrigramPhraseFields.php create mode 100644 Classes/Domain/Search/Query/Query.php create mode 100644 Classes/Domain/Search/Query/QueryBuilder.php create mode 100644 Classes/Domain/Search/Query/SearchQuery.php create mode 100644 Classes/Domain/Search/Query/SuggestQuery.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/AbstractFacet.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/AbstractFacetItem.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/AbstractFacetItemCollection.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/AbstractFacetPackage.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/AbstractFacetParser.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/DefaultFacetQueryBuilder.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/DefaultUrlDecoder.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/FacetCollection.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/FacetParserInterface.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/FacetQueryBuilderInterface.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/FacetRegistry.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/FacetUrlDecoderInterface.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/InvalidFacetPackageException.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/InvalidFacetParserException.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/InvalidQueryBuilderException.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/InvalidUrlDecoderException.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/AbstractOptionFacetItem.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/AbstractOptionsFacet.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Hierarchy/HierarchyFacet.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Hierarchy/HierarchyFacetParser.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Hierarchy/HierarchyPackage.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Hierarchy/HierarchyTool.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Hierarchy/HierarchyUrlDecoder.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Hierarchy/Node.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Hierarchy/NodeCollection.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/OptionCollection.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Options/Option.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Options/OptionsFacet.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Options/OptionsFacetParser.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Options/OptionsFacetQueryBuilder.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/Options/OptionsPackage.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/QueryGroup/Option.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/QueryGroup/QueryGroupFacet.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/QueryGroup/QueryGroupFacetParser.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/QueryGroup/QueryGroupFacetQueryBuilder.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/QueryGroup/QueryGroupPackage.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/OptionBased/QueryGroup/QueryGroupUrlDecoder.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/AbstractRangeFacetItem.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/AbstractRangeFacetParser.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/DateRange/DateRange.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/DateRange/DateRangeCollection.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/DateRange/DateRangeCount.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/DateRange/DateRangeFacet.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/DateRange/DateRangeFacetParser.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/DateRange/DateRangeFacetQueryBuilder.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/DateRange/DateRangePackage.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/DateRange/DateRangeUrlDecoder.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/NumericRange/NumericRange.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/NumericRange/NumericRangeCollection.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/NumericRange/NumericRangeCount.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/NumericRange/NumericRangeFacet.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/NumericRange/NumericRangeFacetParser.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/NumericRange/NumericRangeFacetQueryBuilder.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/NumericRange/NumericRangePackage.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RangeBased/NumericRange/NumericRangeUrlDecoder.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RenderingInstructions/FormatDate.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/RequirementsService.php create mode 100644 Classes/Domain/Search/ResultSet/Facets/SortingExpression.php create mode 100644 Classes/Domain/Search/ResultSet/Grouping/Group.php create mode 100644 Classes/Domain/Search/ResultSet/Grouping/GroupCollection.php create mode 100644 Classes/Domain/Search/ResultSet/Grouping/GroupItem.php create mode 100644 Classes/Domain/Search/ResultSet/Grouping/GroupItemCollection.php create mode 100644 Classes/Domain/Search/ResultSet/Result/Parser/AbstractResultParser.php create mode 100644 Classes/Domain/Search/ResultSet/Result/Parser/DefaultResultParser.php create mode 100644 Classes/Domain/Search/ResultSet/Result/Parser/DocumentEscapeService.php create mode 100644 Classes/Domain/Search/ResultSet/Result/Parser/ResultParserRegistry.php create mode 100644 Classes/Domain/Search/ResultSet/Result/SearchResult.php create mode 100644 Classes/Domain/Search/ResultSet/Result/SearchResultBuilder.php create mode 100644 Classes/Domain/Search/ResultSet/Result/SearchResultCollection.php create mode 100644 Classes/Domain/Search/ResultSet/ResultSetReconstitutionProcessor.php create mode 100644 Classes/Domain/Search/ResultSet/SearchResultSet.php create mode 100644 Classes/Domain/Search/ResultSet/SearchResultSetProcessor.php create mode 100644 Classes/Domain/Search/ResultSet/SearchResultSetService.php create mode 100644 Classes/Domain/Search/ResultSet/Sorting/Sorting.php create mode 100644 Classes/Domain/Search/ResultSet/Sorting/SortingCollection.php create mode 100644 Classes/Domain/Search/ResultSet/Sorting/SortingHelper.php create mode 100644 Classes/Domain/Search/ResultSet/Spellchecking/Suggestion.php create mode 100644 Classes/Domain/Search/Score/Score.php create mode 100644 Classes/Domain/Search/Score/ScoreCalculationService.php create mode 100644 Classes/Domain/Search/SearchRequest.php create mode 100644 Classes/Domain/Search/SearchRequestAware.php create mode 100644 Classes/Domain/Search/SearchRequestBuilder.php create mode 100644 Classes/Domain/Search/Statistics/StatisticsRepository.php create mode 100644 Classes/Domain/Search/Statistics/StatisticsWriterProcessor.php create mode 100644 Classes/Domain/Search/Suggest/SuggestService.php create mode 100644 Classes/Domain/Search/Uri/SearchUriBuilder.php create mode 100644 Classes/Domain/Site/Exception/InvalidSiteConfigurationCombinationException.php create mode 100644 Classes/Domain/Site/Site.php create mode 100644 Classes/Domain/Site/SiteHashService.php create mode 100644 Classes/Domain/Site/SiteInterface.php create mode 100644 Classes/Domain/Site/SiteRepository.php create mode 100644 Classes/Domain/Site/Typo3ManagedSite.php create mode 100644 Classes/Domain/Variants/IdBuilder.php create mode 100644 Classes/Domain/Variants/IdModifier.php create mode 100644 Classes/Domain/Variants/VariantsProcessor.php create mode 100644 Classes/Eid/Api.php create mode 100644 Classes/Eid/SiteHash.php create mode 100644 Classes/FieldProcessor/AbstractHierarchyProcessor.php create mode 100755 Classes/FieldProcessor/CategoryUidToHierarchy.php create mode 100644 Classes/FieldProcessor/FieldProcessor.php create mode 100644 Classes/FieldProcessor/PageUidToHierarchy.php create mode 100644 Classes/FieldProcessor/PathToHierarchy.php create mode 100644 Classes/FieldProcessor/Service.php create mode 100644 Classes/FieldProcessor/TimestampToIsoDate.php create mode 100644 Classes/FieldProcessor/TimestampToUtcIsoDate.php create mode 100644 Classes/FrontendEnvironment.php create mode 100644 Classes/FrontendEnvironment/Tsfe.php create mode 100644 Classes/FrontendEnvironment/TypoScript.php create mode 100644 Classes/GarbageCollector.php create mode 100644 Classes/GarbageCollectorPostProcessor.php create mode 100644 Classes/HtmlContentExtractor.php create mode 100644 Classes/IndexQueue/AbstractIndexer.php create mode 100644 Classes/IndexQueue/AdditionalIndexQueueItemIndexer.php create mode 100644 Classes/IndexQueue/Exception/DocumentPreparationException.php create mode 100644 Classes/IndexQueue/Exception/IndexingException.php create mode 100644 Classes/IndexQueue/FrontendHelper/AbstractFrontendHelper.php create mode 100644 Classes/IndexQueue/FrontendHelper/AuthorizationService.php create mode 100644 Classes/IndexQueue/FrontendHelper/Dispatcher.php create mode 100644 Classes/IndexQueue/FrontendHelper/FrontendHelper.php create mode 100644 Classes/IndexQueue/FrontendHelper/Manager.php create mode 100644 Classes/IndexQueue/FrontendHelper/PageFieldMappingIndexer.php create mode 100644 Classes/IndexQueue/FrontendHelper/PageIndexer.php create mode 100644 Classes/IndexQueue/FrontendHelper/UserGroupDetector.php create mode 100644 Classes/IndexQueue/Indexer.php create mode 100644 Classes/IndexQueue/InitializationPostProcessor.php create mode 100644 Classes/IndexQueue/Initializer/AbstractInitializer.php create mode 100644 Classes/IndexQueue/Initializer/IndexQueueInitializer.php create mode 100644 Classes/IndexQueue/Initializer/Page.php create mode 100644 Classes/IndexQueue/Initializer/Record.php create mode 100644 Classes/IndexQueue/InvalidFieldNameException.php create mode 100644 Classes/IndexQueue/Item.php create mode 100644 Classes/IndexQueue/NoPidException.php create mode 100644 Classes/IndexQueue/PageIndexer.php create mode 100644 Classes/IndexQueue/PageIndexerDataUrlModifier.php create mode 100644 Classes/IndexQueue/PageIndexerDocumentsModifier.php create mode 100644 Classes/IndexQueue/PageIndexerRequest.php create mode 100644 Classes/IndexQueue/PageIndexerRequestHandler.php create mode 100644 Classes/IndexQueue/PageIndexerResponse.php create mode 100644 Classes/IndexQueue/Queue.php create mode 100644 Classes/IndexQueue/RecordMonitor.php create mode 100644 Classes/IndexQueue/SerializedValueDetector.php create mode 100644 Classes/LanguageFileUnavailableException.php create mode 100644 Classes/Middleware/FrontendUserAuthenticator.php create mode 100644 Classes/Middleware/PageIndexerFinisher.php create mode 100644 Classes/Middleware/PageIndexerInitialization.php create mode 100644 Classes/Migrations/Migration.php create mode 100644 Classes/Migrations/RemoveSiteFromScheduler.php create mode 100644 Classes/Mvc/Controller/SolrControllerContext.php create mode 100644 Classes/NoSolrConnectionFoundException.php create mode 100644 Classes/PageDocumentPostProcessor.php create mode 100644 Classes/PingFailedException.php create mode 100644 Classes/Query/Modifier/Elevation.php create mode 100644 Classes/Query/Modifier/Faceting.php create mode 100644 Classes/Query/Modifier/Modifier.php create mode 100644 Classes/Query/Modifier/Statistics.php create mode 100644 Classes/Report/AbstractSolrStatus.php create mode 100644 Classes/Report/AccessFilterPluginInstalledStatus.php create mode 100644 Classes/Report/AllowUrlFOpenStatus.php create mode 100644 Classes/Report/FilterVarStatus.php create mode 100644 Classes/Report/SchemaStatus.php create mode 100644 Classes/Report/SiteHandlingStatus.php create mode 100644 Classes/Report/SolrConfigStatus.php create mode 100644 Classes/Report/SolrConfigurationStatus.php create mode 100644 Classes/Report/SolrStatus.php create mode 100644 Classes/Report/SolrVersionStatus.php create mode 100644 Classes/Response/Processor/ResponseProcessor.php create mode 100644 Classes/Search.php create mode 100644 Classes/Search/AbstractComponent.php create mode 100644 Classes/Search/AccessComponent.php create mode 100644 Classes/Search/AnalysisComponent.php create mode 100644 Classes/Search/DebugComponent.php create mode 100644 Classes/Search/ElevationComponent.php create mode 100644 Classes/Search/FacetingComponent.php create mode 100644 Classes/Search/FacetsModifier.php create mode 100644 Classes/Search/HighlightingComponent.php create mode 100644 Classes/Search/LastSearchesComponent.php create mode 100644 Classes/Search/QueryAware.php create mode 100644 Classes/Search/RelevanceComponent.php create mode 100644 Classes/Search/ResponseModifier.php create mode 100644 Classes/Search/SearchAware.php create mode 100644 Classes/Search/SearchComponent.php create mode 100644 Classes/Search/SearchComponentManager.php create mode 100644 Classes/Search/SortingComponent.php create mode 100644 Classes/Search/SpellcheckingComponent.php create mode 100644 Classes/Search/StatisticsComponent.php create mode 100644 Classes/SubstitutePageIndexer.php create mode 100644 Classes/System/Cache/TwoLevelCache.php create mode 100644 Classes/System/Configuration/ConfigurationManager.php create mode 100644 Classes/System/Configuration/ConfigurationPageResolver.php create mode 100644 Classes/System/Configuration/ExtensionConfiguration.php create mode 100644 Classes/System/Configuration/TypoScriptConfiguration.php create mode 100644 Classes/System/ContentObject/ContentObjectService.php create mode 100644 Classes/System/Data/AbstractCollection.php create mode 100644 Classes/System/Data/DateTime.php create mode 100644 Classes/System/DateTime/FormatService.php create mode 100644 Classes/System/Environment/CliEnvironment.php create mode 100644 Classes/System/Environment/WebRootAllReadyDefinedException.php create mode 100644 Classes/System/Hooks/Backend/Toolbar/ClearCacheActionsHook.php create mode 100644 Classes/System/Language/FrontendOverlayService.php create mode 100644 Classes/System/Logging/DebugWriter.php create mode 100644 Classes/System/Logging/SolrLogManager.php create mode 100644 Classes/System/Mvc/Backend/Component/Exception/InvalidViewObjectNameException.php create mode 100644 Classes/System/Mvc/Backend/ModuleData.php create mode 100644 Classes/System/Mvc/Backend/Service/ModuleDataStorageService.php create mode 100644 Classes/System/Object/AbstractClassRegistry.php create mode 100644 Classes/System/Page/Rootline.php create mode 100644 Classes/System/Records/AbstractRepository.php create mode 100644 Classes/System/Records/Pages/PagesRepository.php create mode 100644 Classes/System/Records/SystemCategory/SystemCategoryRepository.php create mode 100644 Classes/System/Records/SystemLanguage/SystemLanguageRepository.php create mode 100644 Classes/System/Records/SystemTemplate/SystemTemplateRepository.php create mode 100644 Classes/System/Service/ConfigurationService.php create mode 100644 Classes/System/Session/FrontendUserSession.php create mode 100644 Classes/System/Solr/Document/Document.php create mode 100644 Classes/System/Solr/Node.php create mode 100644 Classes/System/Solr/Parser/SchemaParser.php create mode 100644 Classes/System/Solr/Parser/StopWordParser.php create mode 100644 Classes/System/Solr/Parser/SynonymParser.php create mode 100644 Classes/System/Solr/ParsingUtil.php create mode 100644 Classes/System/Solr/RequestFactory.php create mode 100644 Classes/System/Solr/ResponseAdapter.php create mode 100644 Classes/System/Solr/Schema/Schema.php create mode 100644 Classes/System/Solr/Service/AbstractSolrService.php create mode 100644 Classes/System/Solr/Service/SolrAdminService.php create mode 100644 Classes/System/Solr/Service/SolrReadService.php create mode 100644 Classes/System/Solr/Service/SolrWriteService.php create mode 100644 Classes/System/Solr/SolrCommunicationException.php create mode 100644 Classes/System/Solr/SolrConnection.php create mode 100644 Classes/System/Solr/SolrIncompleteResponseException.php create mode 100644 Classes/System/Solr/SolrInternalServerErrorException.php create mode 100644 Classes/System/Solr/SolrUnavailableException.php create mode 100644 Classes/System/TCA/TCAService.php create mode 100644 Classes/System/Url/UrlHelper.php create mode 100644 Classes/System/UserFunctions/FlexFormUserFunctions.php create mode 100644 Classes/System/Util/ArrayAccessor.php create mode 100644 Classes/System/Util/SiteUtility.php create mode 100644 Classes/System/Validator/Path.php create mode 100644 Classes/Task/AbstractMeilisearchTask.php create mode 100644 Classes/Task/IndexQueueWorkerTask.php create mode 100644 Classes/Task/IndexQueueWorkerTaskAdditionalFieldProvider.php create mode 100644 Classes/Task/ReIndexTask.php create mode 100644 Classes/Task/ReIndexTaskAdditionalFieldProvider.php create mode 100644 Classes/Typo3PageContentExtractor.php create mode 100644 Classes/Typo3PageIndexer.php create mode 100644 Classes/Util.php create mode 100644 Classes/Utility/ManagedResourcesUtility.php create mode 100644 Classes/ViewHelpers/AbstractSolrFrontendTagBasedViewHelper.php create mode 100644 Classes/ViewHelpers/AbstractSolrFrontendViewHelper.php create mode 100644 Classes/ViewHelpers/AbstractSolrTagBasedViewHelper.php create mode 100644 Classes/ViewHelpers/AbstractSolrViewHelper.php create mode 100644 Classes/ViewHelpers/Backend/Button/HelpButtonViewHelper.php create mode 100644 Classes/ViewHelpers/Backend/IsStringViewHelper.php create mode 100644 Classes/ViewHelpers/Backend/Security/IfHasAccessToModuleViewHelper.php create mode 100644 Classes/ViewHelpers/Debug/DocumentScoreAnalyzerViewHelper.php create mode 100644 Classes/ViewHelpers/Debug/QueryViewHelper.php create mode 100644 Classes/ViewHelpers/Document/HighlightResultViewHelper.php create mode 100644 Classes/ViewHelpers/Document/RelevanceViewHelper.php create mode 100644 Classes/ViewHelpers/Facet/Area/GroupViewHelper.php create mode 100644 Classes/ViewHelpers/Facet/Options/Group/Prefix/LabelFilterViewHelper.php create mode 100644 Classes/ViewHelpers/Facet/Options/Group/Prefix/LabelPrefixesViewHelper.php create mode 100644 Classes/ViewHelpers/Format/ArrayViewHelper.php create mode 100644 Classes/ViewHelpers/PageBrowserRangeViewHelper.php create mode 100644 Classes/ViewHelpers/SearchFormViewHelper.php create mode 100644 Classes/ViewHelpers/TranslateViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/AbstractUriViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Facet/AbstractValueViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Facet/AddFacetItemViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Facet/RemoveAllFacetsViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Facet/RemoveFacetItemViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Facet/RemoveFacetViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Facet/SetFacetItemViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Paginate/GroupItemPageViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Paginate/ResultPageViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Result/AddSearchWordListViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Search/CurrentSearchViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Search/StartNewSearchViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Sorting/RemoveSortingViewHelper.php create mode 100644 Classes/ViewHelpers/Uri/Sorting/SetSortingViewHelper.php create mode 100644 Classes/ViewHelpers/Widget/Controller/AbstractPaginateWidgetController.php create mode 100644 Classes/ViewHelpers/Widget/Controller/FrequentlySearchedController.php create mode 100644 Classes/ViewHelpers/Widget/Controller/GroupItemPaginateController.php create mode 100644 Classes/ViewHelpers/Widget/Controller/LastSearchesController.php create mode 100644 Classes/ViewHelpers/Widget/Controller/ResultPaginateController.php create mode 100644 Classes/ViewHelpers/Widget/FrequentlySearchedViewHelper.php create mode 100644 Classes/ViewHelpers/Widget/GroupItemPaginateViewHelper.php create mode 100644 Classes/ViewHelpers/Widget/LastSearchesViewHelper.php create mode 100644 Classes/ViewHelpers/Widget/ResultPaginateViewHelper.php create mode 100644 Classes/Widget/AbstractWidgetController.php create mode 100644 Classes/Widget/AbstractWidgetViewHelper.php create mode 100644 Classes/Widget/WidgetRequest.php create mode 100644 Configuration/FlexForms/Form.xml create mode 100644 Configuration/FlexForms/Results.xml create mode 100644 Configuration/RequestMiddlewares.php create mode 100644 Configuration/SiteConfiguration/Overrides/sites.php create mode 100644 Configuration/TCA/Overrides/sys_template.php create mode 100644 Configuration/TCA/Overrides/tt_content.php create mode 100644 Configuration/TSconfig/ContentElementWizard.typoscript create mode 100644 Configuration/TSconfig/Page/Mod/Wizards/NewContentElement.tsconfig create mode 100644 Configuration/TypoScript/BootstrapCss/setup.txt create mode 100644 Configuration/TypoScript/BootstrapCss/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Ajaxify/setup.txt create mode 100644 Configuration/TypoScript/Examples/Ajaxify/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/BoostQueries/setup.txt create mode 100644 Configuration/TypoScript/Examples/BoostQueries/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/ConnectionFromConfVars/setup.txt create mode 100644 Configuration/TypoScript/Examples/ConnectionFromConfVars/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/EverythingOn/setup.txt create mode 100644 Configuration/TypoScript/Examples/EverythingOn/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Facets/DateRange/setup.txt create mode 100644 Configuration/TypoScript/Examples/Facets/DateRange/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Facets/Hierarchy/setup.txt create mode 100644 Configuration/TypoScript/Examples/Facets/Hierarchy/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Facets/NumericRange/setup.txt create mode 100644 Configuration/TypoScript/Examples/Facets/NumericRange/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Facets/Options/setup.txt create mode 100644 Configuration/TypoScript/Examples/Facets/Options/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Facets/OptionsFiltered/setup.txt create mode 100644 Configuration/TypoScript/Examples/Facets/OptionsFiltered/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Facets/OptionsPrefixGrouped/setup.txt create mode 100644 Configuration/TypoScript/Examples/Facets/OptionsPrefixGrouped/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Facets/OptionsSinglemode/setup.txt create mode 100644 Configuration/TypoScript/Examples/Facets/OptionsSinglemode/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Facets/OptionsToggle/setup.txt create mode 100644 Configuration/TypoScript/Examples/Facets/OptionsToggle/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Facets/QueryGroup/setup.txt create mode 100644 Configuration/TypoScript/Examples/Facets/QueryGroup/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/FilterPages/setup.txt create mode 100644 Configuration/TypoScript/Examples/FilterPages/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/IndexQueueNews/setup.txt create mode 100644 Configuration/TypoScript/Examples/IndexQueueNews/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/IndexQueueNewsContentElements/setup.txt create mode 100644 Configuration/TypoScript/Examples/IndexQueueNewsContentElements/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/IndexQueueTtNews/setup.txt create mode 100644 Configuration/TypoScript/Examples/IndexQueueTtNews/setup.typoscript create mode 100644 Configuration/TypoScript/Examples/Suggest/setup.txt create mode 100644 Configuration/TypoScript/Examples/Suggest/setup.typoscript create mode 100644 Configuration/TypoScript/OpenSearch/constants.txt create mode 100644 Configuration/TypoScript/OpenSearch/constants.typoscript create mode 100644 Configuration/TypoScript/OpenSearch/setup.txt create mode 100644 Configuration/TypoScript/OpenSearch/setup.typoscript create mode 100644 Configuration/TypoScript/Solr/constants.txt create mode 100644 Configuration/TypoScript/Solr/constants.typoscript create mode 100644 Configuration/TypoScript/Solr/setup.txt create mode 100644 Configuration/TypoScript/Solr/setup.typoscript create mode 100644 Configuration/TypoScript/StyleSheets/setup.txt create mode 100644 Configuration/TypoScript/StyleSheets/setup.typoscript create mode 100644 LICENSE.txt create mode 100644 PULL_REQUEST_TEMPLATE.md create mode 100644 README.md create mode 100644 Resources/Css/Backend/indexingconfigurationselectorfield.css create mode 100644 Resources/Css/JQueryUi/jquery-ui.custom.css create mode 100644 Resources/Css/ModAdmin/index.css create mode 100644 Resources/Css/Report/index.css create mode 100755 Resources/Images/JQueryUi/ui-anim_basic_16x16.gif create mode 100644 Resources/Images/JQueryUi/ui-bg_glass_55_fcf0ba_1x400.png create mode 100644 Resources/Images/JQueryUi/ui-bg_gloss-wave_100_ece8da_500x100.png create mode 100644 Resources/Images/JQueryUi/ui-bg_highlight-hard_100_f7f7f7_1x100.png create mode 100644 Resources/Images/JQueryUi/ui-bg_highlight-hard_100_fafaf4_1x100.png create mode 100755 Resources/Images/JQueryUi/ui-bg_highlight-hard_15_f18f0b_1x100.png create mode 100644 Resources/Images/JQueryUi/ui-bg_highlight-hard_95_cccccc_1x100.png create mode 100755 Resources/Images/JQueryUi/ui-bg_highlight-soft_25_f18f0b_1x100.png create mode 100644 Resources/Images/JQueryUi/ui-bg_highlight-soft_95_ffedad_1x100.png create mode 100644 Resources/Images/JQueryUi/ui-bg_inset-soft_15_2b2922_1x100.png create mode 100644 Resources/Images/JQueryUi/ui-icons_808080_256x240.png create mode 100644 Resources/Images/JQueryUi/ui-icons_847e71_256x240.png create mode 100644 Resources/Images/JQueryUi/ui-icons_cd0a0a_256x240.png create mode 100755 Resources/Images/JQueryUi/ui-icons_d9a55e_256x240.png create mode 100755 Resources/Images/JQueryUi/ui-icons_e3a345_256x240.png create mode 100644 Resources/Images/JQueryUi/ui-icons_eeeeee_256x240.png create mode 100755 Resources/Images/JQueryUi/ui-icons_ffffff_256x240.png create mode 100644 Resources/Private/.htaccess create mode 100644 Resources/Private/Install/.htaccess create mode 100755 Resources/Private/Install/install-solr.sh create mode 100644 Resources/Private/Language/cn.locallang.xlf create mode 100644 Resources/Private/Language/de.locallang.xlf create mode 100644 Resources/Private/Language/dk.locallang.xlf create mode 100644 Resources/Private/Language/fr.locallang.xlf create mode 100644 Resources/Private/Language/it.locallang.xlf create mode 100644 Resources/Private/Language/jp.locallang.xlf create mode 100644 Resources/Private/Language/kr.locallang.xlf create mode 100644 Resources/Private/Language/locallang.xlf create mode 100644 Resources/Private/Language/locallang_mod.xlf create mode 100644 Resources/Private/Language/locallang_mod_coreoptimize.xlf create mode 100644 Resources/Private/Language/locallang_mod_indexadmin.xlf create mode 100644 Resources/Private/Language/locallang_mod_indexqueue.xlf create mode 100644 Resources/Private/Language/locallang_mod_info.xlf create mode 100644 Resources/Private/Language/nl.locallang.xlf create mode 100644 Resources/Private/Language/pl.locallang.xlf create mode 100644 Resources/Private/Layouts/Backend/WithPageTree.html create mode 100644 Resources/Private/Layouts/Facet.html create mode 100644 Resources/Private/Layouts/Fullwidth.html create mode 100644 Resources/Private/Layouts/Split.html create mode 100644 Resources/Private/Partials/Backend/ApacheSolr/FieldTypesForSingleCore.html create mode 100644 Resources/Private/Partials/Backend/ApacheSolr/SingleDocument.html create mode 100644 Resources/Private/Partials/Backend/FlashMessages.html create mode 100644 Resources/Private/Partials/Backend/NoSiteAvailable.html create mode 100644 Resources/Private/Partials/Facets/Default.html create mode 100644 Resources/Private/Partials/Facets/Hierarchy.html create mode 100644 Resources/Private/Partials/Facets/Options.html create mode 100644 Resources/Private/Partials/Facets/OptionsFiltered.html create mode 100644 Resources/Private/Partials/Facets/OptionsPrefixGrouped.html create mode 100644 Resources/Private/Partials/Facets/OptionsSinglemode.html create mode 100644 Resources/Private/Partials/Facets/OptionsToggle.html create mode 100644 Resources/Private/Partials/Facets/RangeDate.html create mode 100644 Resources/Private/Partials/Facets/RangeNumeric.html create mode 100644 Resources/Private/Partials/Facets/Rootline.html create mode 100644 Resources/Private/Partials/Result/Document.html create mode 100644 Resources/Private/Partials/Result/Facets.html create mode 100644 Resources/Private/Partials/Result/FacetsActive.html create mode 100644 Resources/Private/Partials/Result/PerPage.html create mode 100644 Resources/Private/Partials/Result/RelevanceBar.html create mode 100644 Resources/Private/Partials/Result/Sorting.html create mode 100644 Resources/Private/Partials/Search/Form.html create mode 100644 Resources/Private/Partials/Search/FrequentlySearched.html create mode 100644 Resources/Private/Partials/Search/LastSearches.html create mode 100644 Resources/Private/Php/ComposerLibraries/composer.json create mode 100644 Resources/Private/Php/ComposerLibraries/composer.lock create mode 100644 Resources/Private/Php/strptime/license.pdf create mode 100644 Resources/Private/Php/strptime/strptime.php create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_arabic.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_armenian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_basque.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_brazilian_portuguese.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_bulgarian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_burmese.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_catalan.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_chinese.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_czech.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_danish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_dutch.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_english.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_finnish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_french.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_galician.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_generic.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_german.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_greek.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_hindi.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_hungarian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_indonesian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_irish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_italian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_japanese.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_khmer.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_korean.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_lao.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_latvia.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_norwegian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_persian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_polish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_portuguese.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_romanian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_russian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_serbian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_spanish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_swedish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_thai.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_turkish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_stopwords_ukrainian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_arabic.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_armenian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_basque.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_brazilian_portuguese.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_bulgarian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_burmese.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_catalan.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_chinese.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_czech.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_danish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_dutch.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_english.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_finnish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_french.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_galician.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_generic.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_german.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_greek.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_hindi.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_hungarian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_indonesian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_irish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_italian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_japanese.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_khmer.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_korean.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_lao.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_latvia.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_norwegian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_persian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_polish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_portuguese.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_romanian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_russian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_serbian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_spanish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_swedish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_thai.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_turkish.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/_schema_analysis_synonyms_ukrainian.json create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/admin-extra.html create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/arabic/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/arabic/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/armenian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/armenian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/basque/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/basque/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/brazilian_portuguese/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/brazilian_portuguese/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/bulgarian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/bulgarian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/burmese/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/burmese/readme.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/burmese/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/catalan/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/catalan/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/chinese/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/chinese/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/currency.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/czech/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/czech/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/danish/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/danish/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/dutch/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/dutch/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/elevate.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/english/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/english/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/finnish/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/finnish/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/french/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/french/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/galician/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/galician/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/general_schema_fields.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/general_schema_types.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/generic/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/generic/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/german/german-common-nouns.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/german/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/german/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/greek/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/greek/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/hindi/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/hindi/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/hungarian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/hungarian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/indonesian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/indonesian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/irish/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/irish/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/italian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/italian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/japanese/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/japanese/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/khmer/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/khmer/readme.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/khmer/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/korean/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/korean/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/lao/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/lao/readme.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/lao/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/latvia/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/latvia/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/norwegian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/norwegian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/persian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/persian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/polish/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/polish/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/portuguese/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/portuguese/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/romanian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/romanian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/russian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/russian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/serbian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/serbian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/solrconfig.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/spanish/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/spanish/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/swedish/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/swedish/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/thai/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/thai/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/turkish/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/turkish/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/ukrainian/protwords.txt create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/ukrainian/schema.xml create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/VM_global_library.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/browse.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/doc.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/facet_fields.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/facets.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/footer.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/head.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/header.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/hit.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/hitGrouped.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/jquery.autocomplete.css create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/jquery.autocomplete.js create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/layout.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/main.css create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/query.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/conf/velocity/suggest.vm create mode 100644 Resources/Private/Solr/configsets/ext_solr_11_0_0/typo3lib/solr-typo3-plugin-4.0.0.jar create mode 100644 Resources/Private/Solr/cores/arabic/core.properties create mode 100644 Resources/Private/Solr/cores/armenian/core.properties create mode 100644 Resources/Private/Solr/cores/basque/core.properties create mode 100644 Resources/Private/Solr/cores/brazilian_portuguese/core.properties create mode 100644 Resources/Private/Solr/cores/bulgarian/core.properties create mode 100644 Resources/Private/Solr/cores/burmese/core.properties create mode 100644 Resources/Private/Solr/cores/catalan/core.properties create mode 100644 Resources/Private/Solr/cores/chinese/core.properties create mode 100644 Resources/Private/Solr/cores/czech/core.properties create mode 100644 Resources/Private/Solr/cores/danish/core.properties create mode 100644 Resources/Private/Solr/cores/dutch/core.properties create mode 100644 Resources/Private/Solr/cores/english/core.properties create mode 100644 Resources/Private/Solr/cores/finnish/core.properties create mode 100644 Resources/Private/Solr/cores/french/core.properties create mode 100644 Resources/Private/Solr/cores/galician/core.properties create mode 100644 Resources/Private/Solr/cores/german/core.properties create mode 100644 Resources/Private/Solr/cores/greek/core.properties create mode 100644 Resources/Private/Solr/cores/hindi/core.properties create mode 100644 Resources/Private/Solr/cores/hungarian/core.properties create mode 100644 Resources/Private/Solr/cores/indonesian/core.properties create mode 100644 Resources/Private/Solr/cores/irish/core.properties create mode 100644 Resources/Private/Solr/cores/italian/core.properties create mode 100644 Resources/Private/Solr/cores/japanese/core.properties create mode 100644 Resources/Private/Solr/cores/khmer/core.properties create mode 100644 Resources/Private/Solr/cores/korean/core.properties create mode 100644 Resources/Private/Solr/cores/lao/core.properties create mode 100644 Resources/Private/Solr/cores/latvia/core.properties create mode 100644 Resources/Private/Solr/cores/norwegian/core.properties create mode 100644 Resources/Private/Solr/cores/persian/core.properties create mode 100644 Resources/Private/Solr/cores/polish/core.properties create mode 100644 Resources/Private/Solr/cores/portuguese/core.properties create mode 100644 Resources/Private/Solr/cores/romanian/core.properties create mode 100644 Resources/Private/Solr/cores/russian/core.properties create mode 100644 Resources/Private/Solr/cores/serbian/core.properties create mode 100644 Resources/Private/Solr/cores/spanish/core.properties create mode 100644 Resources/Private/Solr/cores/swedish/core.properties create mode 100644 Resources/Private/Solr/cores/thai/core.properties create mode 100644 Resources/Private/Solr/cores/turkish/core.properties create mode 100644 Resources/Private/Solr/cores/ukrainian/core.properties create mode 100644 Resources/Private/Solr/solr.xml create mode 100644 Resources/Private/Solr/zoo.cfg create mode 100644 Resources/Private/Templates/Backend/PageModule/Summary.html create mode 100644 Resources/Private/Templates/Backend/Reports/AccessFilterPluginInstalledStatusIsOutDated.html create mode 100644 Resources/Private/Templates/Backend/Reports/AccessFilterPluginInstalledStatusNotInstalled.html create mode 100644 Resources/Private/Templates/Backend/Reports/RootPageFlagStatus.html create mode 100644 Resources/Private/Templates/Backend/Reports/SchemaStatus.html create mode 100644 Resources/Private/Templates/Backend/Reports/SiteHandlingStatus.html create mode 100644 Resources/Private/Templates/Backend/Reports/SolrConfigStatus.html create mode 100644 Resources/Private/Templates/Backend/Reports/SolrConfigurationStatusDomainRecord.html create mode 100644 Resources/Private/Templates/Backend/Reports/SolrConfigurationStatusIndexing.html create mode 100644 Resources/Private/Templates/Backend/Reports/SolrStatus.html create mode 100644 Resources/Private/Templates/Backend/Reports/SolrVersionStatus.html create mode 100644 Resources/Private/Templates/Backend/Search/CoreOptimizationModule/Index.html create mode 100644 Resources/Private/Templates/Backend/Search/IndexAdministrationModule/Index.html create mode 100644 Resources/Private/Templates/Backend/Search/IndexQueueModule/Index.html create mode 100644 Resources/Private/Templates/Backend/Search/IndexQueueModule/ShowError.html create mode 100644 Resources/Private/Templates/Backend/Search/InfoModule/DocumentsDetails.html create mode 100644 Resources/Private/Templates/Backend/Search/InfoModule/Index.html create mode 100644 Resources/Private/Templates/Search/Detail.html create mode 100644 Resources/Private/Templates/Search/Form.html create mode 100644 Resources/Private/Templates/Search/FrequentlySearched.html create mode 100644 Resources/Private/Templates/Search/Results.html create mode 100644 Resources/Private/Templates/Search/SolrNotAvailable.html create mode 100644 Resources/Private/Templates/ViewHelpers/Widget/FrequentlySearched/Index.html create mode 100644 Resources/Private/Templates/ViewHelpers/Widget/GroupItemPaginate/Index.html create mode 100644 Resources/Private/Templates/ViewHelpers/Widget/LastSearches/Index.html create mode 100644 Resources/Private/Templates/ViewHelpers/Widget/ResultPaginate/Index.html create mode 100644 Resources/Public/Fonts/Bootstrap/glyphicons-halflings-regular.eot create mode 100644 Resources/Public/Fonts/Bootstrap/glyphicons-halflings-regular.svg create mode 100644 Resources/Public/Fonts/Bootstrap/glyphicons-halflings-regular.ttf create mode 100644 Resources/Public/Fonts/Bootstrap/glyphicons-halflings-regular.woff create mode 100644 Resources/Public/Fonts/Bootstrap/glyphicons-halflings-regular.woff2 create mode 100644 Resources/Public/Icons/Extension.png create mode 100644 Resources/Public/Images/Icons/ContentElement.svg create mode 100644 Resources/Public/Images/Icons/InitSolrConnection.svg create mode 100644 Resources/Public/Images/Icons/InitSolrConnections.svg create mode 100644 Resources/Public/Images/Icons/ModuleCoreOptimization.svg create mode 100644 Resources/Public/Images/Icons/ModuleIndexAdministration.svg create mode 100644 Resources/Public/Images/Icons/ModuleIndexQueue.svg create mode 100644 Resources/Public/Images/Icons/ModuleInfo.svg create mode 100644 Resources/Public/Images/Icons/ModuleSolrMain.svg create mode 100644 Resources/Public/Images/Icons/Search.png create mode 100644 Resources/Public/Images/IndicatorDown.png create mode 100644 Resources/Public/Images/IndicatorUp.png create mode 100644 Resources/Public/Images/SearchButton.gif create mode 100644 Resources/Public/Images/dkd_logo.png create mode 100644 Resources/Public/JavaScript/Bootstrap/bootstrap.js create mode 100644 Resources/Public/JavaScript/Bootstrap/bootstrap.min.js create mode 100644 Resources/Public/JavaScript/Bootstrap/npm.js create mode 100644 Resources/Public/JavaScript/Chart.js create mode 100644 Resources/Public/JavaScript/FormModal.js create mode 100755 Resources/Public/JavaScript/JQuery/URI.min.js create mode 100644 Resources/Public/JavaScript/JQuery/jquery-ui.min.js create mode 100755 Resources/Public/JavaScript/JQuery/jquery.URI.min.js create mode 100644 Resources/Public/JavaScript/JQuery/jquery.autocomplete.min.js create mode 100644 Resources/Public/JavaScript/JQuery/jquery.min.js create mode 100644 Resources/Public/JavaScript/JQuery/ui-i18n/jquery.ui.datepicker-de.js create mode 100644 Resources/Public/JavaScript/JQuery/ui-i18n/jquery.ui.datepicker-fr.js create mode 100644 Resources/Public/JavaScript/JQuery/ui-i18n/jquery.ui.datepicker-nl.js create mode 100644 Resources/Public/JavaScript/SearchStatistics.js create mode 100644 Resources/Public/JavaScript/facet_daterange_controller.js create mode 100644 Resources/Public/JavaScript/facet_numericrange_controller.js create mode 100644 Resources/Public/JavaScript/facet_options_controller.js create mode 100644 Resources/Public/JavaScript/search_controller.js create mode 100644 Resources/Public/JavaScript/suggest_controller.js create mode 100644 Resources/Public/StyleSheets/Backend/IndexQueueModule.css create mode 100644 Resources/Public/StyleSheets/Backend/IndexingConfigurationSelectorField.css create mode 100644 Resources/Public/StyleSheets/Frontend/Bootstrap/bootstrap-theme.css create mode 100644 Resources/Public/StyleSheets/Frontend/Bootstrap/bootstrap-theme.css.map create mode 100644 Resources/Public/StyleSheets/Frontend/Bootstrap/bootstrap-theme.min.css create mode 100644 Resources/Public/StyleSheets/Frontend/Bootstrap/bootstrap-theme.min.css.map create mode 100644 Resources/Public/StyleSheets/Frontend/Bootstrap/bootstrap.css create mode 100644 Resources/Public/StyleSheets/Frontend/Bootstrap/bootstrap.css.map create mode 100644 Resources/Public/StyleSheets/Frontend/Bootstrap/bootstrap.min.css create mode 100644 Resources/Public/StyleSheets/Frontend/Bootstrap/bootstrap.min.css.map create mode 100644 Resources/Public/StyleSheets/Frontend/loader.css create mode 100644 Resources/Public/StyleSheets/Frontend/results.css create mode 100644 Resources/Public/StyleSheets/Frontend/suggest.css create mode 100644 Resources/Public/StyleSheets/ModuleAdministration.css create mode 100644 class.ext_update.php create mode 100644 composer.json create mode 100644 ext_conf_template.txt create mode 100644 ext_emconf.php create mode 100644 ext_localconf.php create mode 100644 ext_tables.php create mode 100644 ext_tables.sql diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..85756e4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +# EditorConfig is awesome: http://EditorConfig.org +# TYPO3 Standard: https://github.com/TYPO3/TYPO3.CMS/blob/master/.editorconfig + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +# ReST-Files +[*.rst] +indent_size = 3 +max_line_length = 80 + +# YAML-Files +[*.{yaml,yml}] +indent_size = 2 + +# TypoScript +[*.{typoscript,tsconfig}] +indent_size = 2 + +# XLF/XML-Files +[*.{xlf,xml}] +indent_style = tab diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..03d2232 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ["https://wappler.systems/"] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..cf2d99c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] Please add a speaking title" +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Used versions (please complete the following information):** + - TYPO3 Version: [e.g. 10.4.13] + - Browser: [e.g. chrome, safari] + - EXT:meilisearch Version: [e.g. 10.0.0] + - Used Meilisearch Version: [e.g. 8.8.0] + - PHP Version: [e.g. 7.4.0] + - MySQL Version: [e.g. 8.0.0] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..97ef321 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE] Please describe your feature wish here" +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + +**Target versions** +Please add the EXT:meilisearch target versions here. diff --git a/.github/ISSUE_TEMPLATE/task.md b/.github/ISSUE_TEMPLATE/task.md new file mode 100644 index 0000000..26b2e1b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/task.md @@ -0,0 +1,10 @@ +--- +name: Task +about: This template is used to decribe regular tasks +title: "[TASK]" +labels: '' +assignees: '' + +--- + +**What should be done in the scope of this task?** diff --git a/.github/no-response.yml b/.github/no-response.yml new file mode 100644 index 0000000..0b527af --- /dev/null +++ b/.github/no-response.yml @@ -0,0 +1,14 @@ +# Configuration for probot-no-response - https://github.com/probot/no-response + +# Number of days of inactivity before an Issue is closed for lack of response +daysUntilClose: 28 +# Label requiring a response +responseRequiredLabel: more-information-needed +# Comment to post when closing an Issue for lack of response. Set to `false` to disable +closeComment: > + This issue has been automatically closed because there has been no response + to our request for more information from the original author. With only the + information that is currently in the issue, we don't have enough information + to take action. Please reach out if you have or find the answers we need so + that we can investigate further. + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81bf9a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +.buildpath +.project/ +.settings/ +.idea/ +atlassian-ide-plugin.xml +.DS_Store +index.php +typo3 +typo3_src +typo3conf +typo3temp +uploads +vendor +/composer.lock +.Build +Documentation/_make + +.php_cs.cache diff --git a/Classes/AbstractDataHandlerListener.php b/Classes/AbstractDataHandlerListener.php new file mode 100644 index 0000000..5305f50 --- /dev/null +++ b/Classes/AbstractDataHandlerListener.php @@ -0,0 +1,217 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\Domain\Index\Queue\RecordMonitor\Helper\ConfigurationAwareRecordService; +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\Database\QueryGenerator; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Changes in TYPO3 have an impact on the solr content and are caught + * by the GarbageCollector and RecordMonitor. Both act as a TCE Main Hook. + * + * This base class is used to share functionality that are needed for both + * to perform the changes in the data handler on the solr index. + * + * @author Timo Schmidt + */ +abstract class AbstractDataHandlerListener +{ + /** + * Reference to the configuration manager + * + * @var \WapplerSystems\Meilisearch\Domain\Index\Queue\RecordMonitor\Helper\ConfigurationAwareRecordService + */ + protected $configurationAwareRecordService; + + /** + * @var FrontendEnvironment + */ + protected $frontendEnvironment = null; + + /** + * AbstractDataHandlerListener constructor. + * @param ConfigurationAwareRecordService|null $recordService + */ + public function __construct(ConfigurationAwareRecordService $recordService = null, FrontendEnvironment $frontendEnvironment = null) + { + $this->configurationAwareRecordService = $recordService ?? GeneralUtility::makeInstance(ConfigurationAwareRecordService::class); + $this->frontendEnvironment = $frontendEnvironment ?? GeneralUtility::makeInstance(FrontendEnvironment::class); + } + + /** + * @return array + */ + protected function getAllRelevantFieldsForCurrentState() + { + $allCurrentStateFieldnames = []; + + foreach ($this->getUpdateSubPagesRecursiveTriggerConfiguration() as $triggerConfiguration) { + if (!isset($triggerConfiguration['currentState']) || !is_array($triggerConfiguration['currentState'])) { + // when no "currentState" configuration for the trigger exists we can skip it + continue; + } + + // we collect the currentState fields to return a unique list of all fields + $allCurrentStateFieldnames = array_merge($allCurrentStateFieldnames, array_keys($triggerConfiguration['currentState'])); + } + + return array_unique($allCurrentStateFieldnames); + } + + /** + * When the extend to subpages flag was set, we determine the affected subpages and return them. + * + * @param int $pageId + * @return array + */ + protected function getSubPageIds($pageId) + { + /** @var $queryGenerator \TYPO3\CMS\Core\Database\QueryGenerator */ + $queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class); + + // here we retrieve only the subpages of this page because the permission clause is not evaluated + // on the root node. + $permissionClause = ' 1 ' . BackendUtility::BEenableFields('pages'); + $treePageIdList = $queryGenerator->getTreeList($pageId, 20, 0, $permissionClause); + $treePageIds = array_map('intval', explode(',', $treePageIdList)); + + // the first one can be ignored because this is the page itself + array_shift($treePageIds); + + return $treePageIds; + } + + /** + * Checks if a page update will trigger a recursive update of pages + * + * This can either be the case if some $changedFields are part of the RecursiveUpdateTriggerConfiguration or + * columns have explicitly been configured via plugin.tx_meilisearch.index.queue.recursiveUpdateFields + * + * @param int $pageId + * @param array $changedFields + * @return bool + */ + protected function isRecursivePageUpdateRequired($pageId, $changedFields) + { + // First check RecursiveUpdateTriggerConfiguration + $isRecursiveUpdateRequired = $this->isRecursiveUpdateRequired($pageId, $changedFields); + // If RecursiveUpdateTriggerConfiguration is false => check if changeFields are part of recursiveUpdateFields + if ($isRecursiveUpdateRequired === false) { + $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId($pageId); + $indexQueueConfigurationName = $this->configurationAwareRecordService->getIndexingConfigurationName('pages', $pageId, $solrConfiguration); + if ($indexQueueConfigurationName === null) { + return false; + } + $updateFields = $solrConfiguration->getIndexQueueConfigurationRecursiveUpdateFields($indexQueueConfigurationName); + + // Check if no additional fields have been defined and then skip recursive update + if (empty($updateFields)) { + return false; + } + // If the recursiveUpdateFields configuration is not part of the $changedFields skip recursive update + if (!array_intersect_key($changedFields, $updateFields)) { + return false; + } + } + + return true; + } + + /** + * @param int $pageId + * @param array $changedFields + * @return bool + */ + protected function isRecursiveUpdateRequired($pageId, $changedFields) + { + $fieldsForCurrentState = $this->getAllRelevantFieldsForCurrentState(); + $fieldListToRetrieve = implode(',', $fieldsForCurrentState); + $page = BackendUtility::getRecord('pages', $pageId, $fieldListToRetrieve, '', false); + foreach ($this->getUpdateSubPagesRecursiveTriggerConfiguration() as $triggerConfiguration) { + $allCurrentStateFieldsMatch = $this->getAllCurrentStateFieldsMatch($triggerConfiguration, $page); + $allChangeSetValuesMatch = $this->getAllChangeSetValuesMatch($triggerConfiguration, $changedFields); + + $aMatchingTriggerHasBeenFound = $allCurrentStateFieldsMatch && $allChangeSetValuesMatch; + if ($aMatchingTriggerHasBeenFound) { + return true; + } + } + + return false; + } + + /** + * @param array $triggerConfiguration + * @param array $pageRecord + * @return bool + */ + protected function getAllCurrentStateFieldsMatch($triggerConfiguration, $pageRecord) + { + $triggerConfigurationHasNoCurrentStateConfiguration = !array_key_exists('currentState', $triggerConfiguration); + if ($triggerConfigurationHasNoCurrentStateConfiguration) { + return true; + } + $diff = array_diff_assoc($triggerConfiguration['currentState'], $pageRecord); + return empty($diff); + } + + /** + * @param array $triggerConfiguration + * @param array $changedFields + * @return bool + */ + protected function getAllChangeSetValuesMatch($triggerConfiguration, $changedFields) + { + $triggerConfigurationHasNoChangeSetStateConfiguration = !array_key_exists('changeSet', $triggerConfiguration); + if ($triggerConfigurationHasNoChangeSetStateConfiguration) { + return true; + } + + $diff = array_diff_assoc($triggerConfiguration['changeSet'], $changedFields); + return empty($diff); + } + + /** + * The implementation of this method need to retrieve a configuration to determine which record data + * and change combination required a recursive change. + * + * The structure needs to be: + * + * [ + * [ + * 'currentState' => ['fieldName1' => 'value1'], + * 'changeSet' => ['fieldName1' => 'value1'] + * ] + * ] + * + * When the all values of the currentState AND all values of the changeSet match, a recursive update + * will be triggered. + * + * @return array + */ + abstract protected function getUpdateSubPagesRecursiveTriggerConfiguration(); +} diff --git a/Classes/Access/Rootline.php b/Classes/Access/Rootline.php new file mode 100644 index 0000000..eaec855 --- /dev/null +++ b/Classes/Access/Rootline.php @@ -0,0 +1,228 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\RootlineUtility; +use TYPO3\CMS\Frontend\Page\PageRepository; + +/** + * "Access Rootline", represents all pages and specifically those setting + * frontend user group access restrictions in a page's rootline. + * + * The access rootline only contains pages which set frontend user access + * restrictions and extend them to sub-pages. The format is as follows: + * + * pageId1:group1,group2/pageId2:group3/c:group1,group4,groupN + * + * The single elements of the access rootline are separated by a slash + * character. All but the last elements represent pages, the last element + * defines the access restrictions applied to the page's content elements + * and records shown on the page. + * Each page element is composed by the page ID of the page setting frontend + * user access restrictions, a colon, and a comma separated list of frontend + * user group IDs restricting access to the page. + * The content access element does not have a page ID, instead it replaces + * the ID by a lower case C. + * + * The groups for page elements are compared using OR, so the user needs to be + * a member of only one of the groups listed for a page. The elements are + * checked combined using AND, so the user must be member of at least one + * group in each page element. However, the groups in the content access + * element are checked using AND. So the user must be member of all the groups + * listed in the content access element to see the document. + * + * An access rootline for a generic record could instead be short like this: + * + * r:group1,group2,groupN + * + * In this case the lower case R tells us that we're dealing with a record + * like tt_news or the like. For records the groups are checked using OR + * instead of using AND as it would be the case with content elements. + * + * @author Ingo Renner + */ +class Rootline +{ + + /** + * Delimiter for page and content access right elements in the rootline. + * + * @var string + */ + const ELEMENT_DELIMITER = '/'; + + /** + * Storage for access rootline elements + * + * @var array + */ + protected $rootlineElements = []; + + /** + * Constructor, turns a string representation of an access rootline into an + * object representation. + * + * @param string $accessRootline Access Rootline String representation. + */ + public function __construct($accessRootline = null) + { + if (!is_null($accessRootline)) { + $rawRootlineElements = explode(self::ELEMENT_DELIMITER, $accessRootline); + foreach ($rawRootlineElements as $rawRootlineElement) { + try { + $this->push(GeneralUtility::makeInstance(RootlineElement::class, /** @scrutinizer ignore-type */ $rawRootlineElement)); + } catch (RootlineElementFormatException $e) { + // just ignore the faulty element for now, might log this later + } + } + } + } + + /** + * Adds an Access Rootline Element to the end of the rootline. + * + * @param RootlineElement $rootlineElement Element to add. + */ + public function push(RootlineElement $rootlineElement) + { + $lastElementIndex = max(0, (count($this->rootlineElements) - 1)); + + if (!empty($this->rootlineElements[$lastElementIndex])) { + if ($this->rootlineElements[$lastElementIndex]->getType() == RootlineElement::ELEMENT_TYPE_CONTENT) { + throw new RootlineElementFormatException( + 'Can not add an element to an Access Rootline whose\' last element is a content type element.', + 1294422132 + ); + } + + if ($this->rootlineElements[$lastElementIndex]->getType() == RootlineElement::ELEMENT_TYPE_RECORD) { + throw new RootlineElementFormatException( + 'Can not add an element to an Access Rootline whose\' last element is a record type element.', + 1308343423 + ); + } + } + + $this->rootlineElements[] = $rootlineElement; + } + + /** + * Gets the Access Rootline for a specific page Id. + * + * @param int $pageId The page Id to generate the Access Rootline for. + * @param string $mountPointParameter The mount point parameter for generating the rootline. + * @return \WapplerSystems\Meilisearch\Access\Rootline Access Rootline for the given page Id. + */ + public static function getAccessRootlineByPageId( + $pageId, + $mountPointParameter = '' + ) { + $accessRootline = GeneralUtility::makeInstance(Rootline::class); + $rootlineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $pageId, $mountPointParameter); + try { + $rootline = $rootlineUtility->get(); + } catch (\RuntimeException $e) { + $rootline = []; + } + $rootline = array_reverse($rootline); + // parent pages + foreach ($rootline as $pageRecord) { + if ($pageRecord['fe_group'] + && $pageRecord['extendToSubpages'] + && $pageRecord['uid'] != $pageId + ) { + $accessRootline->push(GeneralUtility::makeInstance( + RootlineElement::class, + /** @scrutinizer ignore-type */ $pageRecord['uid'] . RootlineElement::PAGE_ID_GROUP_DELIMITER . $pageRecord['fe_group'] + )); + } + } + + /** @var $pageSelector PageRepository */ + $pageSelector = GeneralUtility::makeInstance(PageRepository::class); + + // current page + $currentPageRecord = $pageSelector->getPage($pageId, true); + if ($currentPageRecord['fe_group']) { + $accessRootline->push(GeneralUtility::makeInstance( + RootlineElement::class, + /** @scrutinizer ignore-type */ $currentPageRecord['uid'] . RootlineElement::PAGE_ID_GROUP_DELIMITER . $currentPageRecord['fe_group'] + )); + } + + return $accessRootline; + } + + /** + * Returns the string representation of the access rootline. + * + * @return string String representation of the access rootline. + */ + public function __toString() + { + $stringElements = []; + + foreach ($this->rootlineElements as $rootlineElement) { + $stringElements[] = (string)$rootlineElement; + } + + return implode(self::ELEMENT_DELIMITER, $stringElements); + } + + /** + * Gets a the groups in the Access Rootline. + * + * @return array An array of sorted, unique user group IDs required to access a page. + */ + public function getGroups() + { + $groups = []; + + foreach ($this->rootlineElements as $rootlineElement) { + $rootlineElementGroups = $rootlineElement->getGroups(); + $groups = array_merge($groups, $rootlineElementGroups); + } + + $groups = $this->cleanGroupArray($groups); + + return $groups; + } + + /** + * Cleans an array of frontend user group IDs. Removes duplicates and sorts + * the array. + * + * @param array $groups An array of frontend user group IDs + * @return array An array of cleaned frontend user group IDs, unique, sorted. + */ + public static function cleanGroupArray(array $groups) + { + $groups = array_unique($groups); // removes duplicates + sort($groups, SORT_NUMERIC); // sort + + return $groups; + } +} diff --git a/Classes/Access/RootlineElement.php b/Classes/Access/RootlineElement.php new file mode 100644 index 0000000..b2b748e --- /dev/null +++ b/Classes/Access/RootlineElement.php @@ -0,0 +1,187 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * An element in the "Access Rootline". Represents the frontend user group + * access restrictions for a page, a page's content, or a generic record. + * + * @author Ingo Renner + */ +class RootlineElement +{ + + /** + * Page access rootline element. + * + * @var int + */ + const ELEMENT_TYPE_PAGE = 1; + + /** + * Content access rootline element. + * + * @var int + */ + const ELEMENT_TYPE_CONTENT = 2; + + /** + * Record access rootline element. + * + * @var int + */ + const ELEMENT_TYPE_RECORD = 3; + + /** + * Delimiter between the page ID and the groups set for a page. + * + * @var string + */ + const PAGE_ID_GROUP_DELIMITER = ':'; + + /** + * Access type, either page (default) or content. Depending on the type, + * access is granted differently. For pages the user must meet at least one + * group requirement, for content all group requirements must be met. + * + * @var int + */ + protected $type = self::ELEMENT_TYPE_PAGE; + + /** + * Page Id for the element. NULL for the content type. + * + * @var int + */ + protected $pageId = null; + + /** + * Set of access groups assigned to the element. + * + * @var array + */ + protected $accessGroups = []; + + /** + * Constructor for RootlineElement. + * + * @param string $element String representation of an element in the access rootline, usually of the form pageId:commaSeparatedPageAccessGroups + * @throws RootlineElementFormatException on wrong access format. + */ + public function __construct($element) + { + $elementAccess = explode(self::PAGE_ID_GROUP_DELIMITER, $element); + + if (count($elementAccess) === 1 || $elementAccess[0] === 'c') { + // the content access groups part of the access rootline + $this->type = self::ELEMENT_TYPE_CONTENT; + + if (count($elementAccess) === 1) { + $elementGroups = $elementAccess[0]; + } else { + $elementGroups = $elementAccess[1]; + } + } elseif ($elementAccess[0] === 'r') { + // record element type + if (count($elementAccess) !== 2) { + throw new RootlineElementFormatException( + 'Wrong Access Rootline Element format for a record type element.', + 1308342937 + ); + } + + $this->type = self::ELEMENT_TYPE_RECORD; + $elementGroups = $elementAccess[1]; + } else { + // page element type + if (count($elementAccess) !== 2 || !is_numeric($elementAccess[0])) { + throw new RootlineElementFormatException( + 'Wrong Access Rootline Element format for a page type element.', + 1294421105 + ); + } + + $this->pageId = intval($elementAccess[0]); + $elementGroups = $elementAccess[1]; + } + + $this->accessGroups = GeneralUtility::intExplode(',', $elementGroups); + } + + /** + * Returns the String representation of an access rootline element. + * + * @return string Access Rootline Element string representation + */ + public function __toString() + { + $rootlineElement = ''; + + if ($this->type == self::ELEMENT_TYPE_CONTENT) { + $rootlineElement .= 'c'; + } elseif ($this->type == self::ELEMENT_TYPE_RECORD) { + $rootlineElement .= 'r'; + } else { + $rootlineElement .= $this->pageId; + } + + $rootlineElement .= self::PAGE_ID_GROUP_DELIMITER; + $rootlineElement .= implode(',', $this->accessGroups); + + return $rootlineElement; + } + + /** + * Gets the access rootline element's type. + * + * @return int ELEMENT_TYPE_PAGE for page, ELEMENT_TYPE_CONTENT for content access rootline elements + */ + public function getType() + { + return $this->type; + } + + /** + * Gets the page Id for page type elements. + * + * @return int Page Id. + */ + public function getPageId() + { + return $this->pageId; + } + + /** + * Gets the element's access group restrictions. + * + * @return array Array of user group Ids + */ + public function getGroups() + { + return $this->accessGroups; + } +} diff --git a/Classes/Access/RootlineElementFormatException.php b/Classes/Access/RootlineElementFormatException.php new file mode 100644 index 0000000..c805f43 --- /dev/null +++ b/Classes/Access/RootlineElementFormatException.php @@ -0,0 +1,34 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Signals a wrong format for the access definition of a page or the content. + * + * @author Ingo Renner + */ +class RootlineElementFormatException extends \InvalidArgumentException +{ +} diff --git a/Classes/AdditionalFieldsIndexer.php b/Classes/AdditionalFieldsIndexer.php new file mode 100644 index 0000000..7654868 --- /dev/null +++ b/Classes/AdditionalFieldsIndexer.php @@ -0,0 +1,126 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ +use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration; +use WapplerSystems\Meilisearch\System\ContentObject\ContentObjectService; +use WapplerSystems\Meilisearch\System\Solr\Document\Document; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Additional fields indexer. + * + * @todo Move this to an Index Queue frontend helper + * + * Adds page document fields as configured in + * plugin.tx_meilisearch.index.additionalFields. + * + * @author Ingo Renner + */ +class AdditionalFieldsIndexer implements SubstitutePageIndexer +{ + + /** + * @var TypoScriptConfiguration + */ + protected $configuration; + + /** + * @var array + */ + protected $additionalIndexingFields = []; + + /** + * @var array + */ + protected $additionalFieldNames = []; + + /** + * @var ContentObjectService + */ + protected $contentObjectService = null; + + /** + * @param TypoScriptConfiguration $configuration + * @param ContentObjectService $contentObjectService + */ + public function __construct(TypoScriptConfiguration $configuration = null, ContentObjectService $contentObjectService = null) + { + $this->configuration = $configuration === null ? Util::getSolrConfiguration() : $configuration; + $this->additionalIndexingFields = $this->configuration->getIndexAdditionalFieldsConfiguration(); + $this->additionalFieldNames = $this->configuration->getIndexMappedAdditionalFieldNames(); + $this->contentObjectService = $contentObjectService === null ? GeneralUtility::makeInstance(ContentObjectService::class) : $contentObjectService; + } + + /** + * Returns a substitute document for the currently being indexed page. + * + * Uses the original document and adds fields as defined in + * plugin.tx_meilisearch.index.additionalFields. + * + * @param Document $pageDocument The original page document. + * @return Document A Apache Solr Document object that replace the default page document + */ + public function getPageDocument(Document $pageDocument) + { + $substitutePageDocument = clone $pageDocument; + $additionalFields = $this->getAdditionalFields(); + + foreach ($additionalFields as $fieldName => $fieldValue) { + if (!isset($pageDocument->{$fieldName})) { + // making sure we only _add_ new fields + $substitutePageDocument->setField($fieldName, $fieldValue); + } + } + + return $substitutePageDocument; + } + + /** + * Gets the additional fields as an array mapping field names to values. + * + * @return array An array mapping additional field names to their values. + */ + protected function getAdditionalFields() + { + $additionalFields = []; + + foreach ($this->additionalFieldNames as $additionalFieldName) { + $additionalFields[$additionalFieldName] = $this->getFieldValue($additionalFieldName); + } + + return $additionalFields; + } + + /** + * Uses the page's cObj instance to resolve the additional field's value. + * + * @param string $fieldName The name of the field to get. + * @return string The field's value. + */ + protected function getFieldValue($fieldName) + { + return $this->contentObjectService->renderSingleContentObjectByArrayAndKey($this->additionalIndexingFields, $fieldName); + } +} diff --git a/Classes/AdditionalPageIndexer.php b/Classes/AdditionalPageIndexer.php new file mode 100644 index 0000000..bfa62a0 --- /dev/null +++ b/Classes/AdditionalPageIndexer.php @@ -0,0 +1,49 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * A copy is found in the textfile GPL.txt and important notices to the license + * from the author is found in LICENSE.txt distributed with these scripts. + * + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\System\Solr\Document\Document; + +/** + * Interface that defines the method an indexer must implement to provide + * additional documents to index for a page being indexed. + * + * @author Ingo Renner + */ +interface AdditionalPageIndexer +{ + + /** + * Provides additional documents that should be indexed together with a page. + * + * @param Document $pageDocument The original page document. + * @param array $allDocuments An array containing all the documents collected until here, including the page document + * @return array An array of additional \WapplerSystems\Meilisearch\System\Solr\Document\Document objects + */ + public function getAdditionalPageDocuments(Document $pageDocument, array $allDocuments); +} diff --git a/Classes/Api.php b/Classes/Api.php new file mode 100644 index 0000000..8b0d9b1 --- /dev/null +++ b/Classes/Api.php @@ -0,0 +1,58 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Remote API related methods + * + * @author Ingo Renner + */ +class Api +{ + + /** + * Checks whether a string is a valid API key. + * + * @param string $apiKey API key to check for validity + * @return bool TRUE if the API key is valid, FALSE otherwise + */ + public static function isValidApiKey($apiKey) + { + return ($apiKey === self::getApiKey()); + } + + /** + * Generates the API key for the REST API + * + * @return string API key for this installation + */ + public static function getApiKey() + { + return sha1( + $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . + 'tx_meilisearch_api' + ); + } +} diff --git a/Classes/Backend/IndexingConfigurationSelectorField.php b/Classes/Backend/IndexingConfigurationSelectorField.php new file mode 100644 index 0000000..0bcbc6d --- /dev/null +++ b/Classes/Backend/IndexingConfigurationSelectorField.php @@ -0,0 +1,213 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\Domain\Site\Site; +use TYPO3\CMS\Backend\Form\FormResultCompiler; +use TYPO3\CMS\Backend\Form\NodeFactory; +use TYPO3\CMS\Core\Imaging\IconFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Index Queue indexing configuration selector form field. + * + * @author Ingo Renner + */ +class IndexingConfigurationSelectorField +{ + + /** + * Site used to determine indexing configurations + * + * @var Site + */ + protected $site; + + /** + * Form element name + * + * @var string + */ + protected $formElementName = 'tx_meilisearch-index-queue-indexing-configuration-selector'; + + /** + * Selected values + * + * @var array + */ + protected $selectedValues = []; + + /** + * Constructor + * + * @param Site $site The site to use to determine indexing configurations + */ + public function __construct(Site $site = null) + { + $this->site = $site; + } + + /** + * Sets the form element name. + * + * @param string $formElementName Form element name + */ + public function setFormElementName($formElementName) + { + $this->formElementName = $formElementName; + } + + /** + * Gets the form element name. + * + * @return string form element name + */ + public function getFormElementName() + { + return $this->formElementName; + } + + /** + * Sets the selected values. + * + * @param array $selectedValues + */ + public function setSelectedValues(array $selectedValues) + { + $this->selectedValues = $selectedValues; + } + + /** + * Gets the selected values. + * + * @return array + */ + public function getSelectedValues() + { + return $this->selectedValues; + } + + /** + * Renders a field to select which indexing configurations to initialize. + * + * Uses \TYPO3\CMS\Backend\Form\FormEngine. + * + * @return string Markup for the select field + */ + public function render() + { + // transform selected values into the format used by TCEforms + $selectedValues = $this->selectedValues; + $tablesToIndex = $this->getIndexQueueConfigurationTableMap(); + + $formField = $this->renderSelectCheckbox($this->buildSelectorItems($tablesToIndex), $selectedValues); + + // need to wrap the field in a TCEforms table to make the CSS apply + $form[] = '
'; + $form[] = $formField; + $form[] = '
'; + + return implode(LF, $form); + } + + /** + * Builds a map of indexing configuration names to tables to to index. + * + * @return array Indexing configuration to database table map + */ + protected function getIndexQueueConfigurationTableMap() + { + $indexingTableMap = []; + + $solrConfiguration = $this->site->getSolrConfiguration(); + $configurationNames = $solrConfiguration->getEnabledIndexQueueConfigurationNames(); + foreach ($configurationNames as $configurationName) { + $indexingTableMap[$configurationName] = $solrConfiguration->getIndexQueueTableNameOrFallbackToConfigurationName($configurationName); + } + + return $indexingTableMap; + } + + /** + * Builds the items to render in the TCEforms select field. + * + * @param array $tablesToIndex A map of indexing configuration to database tables + * + * @return array Selectable items for the TCEforms select field + */ + protected function buildSelectorItems(array $tablesToIndex) + { + $selectorItems = []; + $iconFactory = GeneralUtility::makeInstance(IconFactory::class); + + foreach ($tablesToIndex as $configurationName => $tableName) { + $icon = $iconFactory->mapRecordTypeToIconIdentifier($tableName, []); + + $labelTableName = ''; + if ($configurationName !== $tableName) { + $labelTableName = ' (' . $tableName . ')'; + } + + $selectorItems[] = [$configurationName . $labelTableName, $configurationName, $icon]; + } + + return $selectorItems; + } + + /** + * @param array $items + * @param string $selectedValues + * + * @return string + * @throws \TYPO3\CMS\Backend\Form\Exception + */ + protected function renderSelectCheckbox($items, $selectedValues) + { + $parameterArray = [ + 'fieldChangeFunc' => [], + 'itemFormElName' => $this->formElementName, + 'itemFormElValue' => $selectedValues, + 'fieldConf' => ['config' => ['items' => $items]], + 'fieldTSConfig' => ['noMatchingValue_label' => ''] + ]; + + $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class); + $options = [ + 'renderType' => 'selectCheckBox', 'table' => 'tx_meilisearch_classes_backend_indexingconfigurationselector', + 'fieldName' => 'additionalFields', 'databaseRow' => [], 'parameterArray' => $parameterArray + ]; + $options['parameterArray']['fieldConf']['config']['items'] = $items; + $options['parameterArray']['fieldTSConfig']['noMatchingValue_label'] = ''; + + $selectCheckboxResult = $nodeFactory->create($options)->render(); + $formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class); + $formResultCompiler->mergeResult($selectCheckboxResult); + + $formHtml = isset($selectCheckboxResult['html']) ? $selectCheckboxResult['html'] : ''; + $content = $formResultCompiler->addCssFiles() . $formHtml . $formResultCompiler->printNeededJSFunctions(); + + return $content; + } +} diff --git a/Classes/Backend/SiteSelectorField.php b/Classes/Backend/SiteSelectorField.php new file mode 100644 index 0000000..003b6f0 --- /dev/null +++ b/Classes/Backend/SiteSelectorField.php @@ -0,0 +1,72 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\Domain\Site\SiteRepository; +use WapplerSystems\Meilisearch\Domain\Site\Site; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * SiteSelectorField + * + * Responsible for generating SiteSelectorField + * + * @author Thomas Hohn + */ +class SiteSelectorField +{ + /** + * Creates a dropdown selector of available TYPO3 sites with Solr configured. + * + * @param string $selectorName Name to be used in the select's name attribute + * @param Site $selectedSite Optional, currently selected site + * @return string Site selector HTML code + */ + public function getAvailableSitesSelector( + $selectorName, + Site $selectedSite = null + ) { + $siteRepository = GeneralUtility::makeInstance(SiteRepository::class); + + $sites = $siteRepository->getAvailableSites(); + $selector = ''; + + return $selector; + } +} diff --git a/Classes/ConnectionManager.php b/Classes/ConnectionManager.php new file mode 100644 index 0000000..fe95cdf --- /dev/null +++ b/Classes/ConnectionManager.php @@ -0,0 +1,267 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\Domain\Site\Site; +use WapplerSystems\Meilisearch\Domain\Site\SiteRepository; +use WapplerSystems\Meilisearch\System\Records\Pages\PagesRepository as PagesRepositoryAtExtSolr; +use WapplerSystems\Meilisearch\System\Records\SystemLanguage\SystemLanguageRepository; +use WapplerSystems\Meilisearch\System\Solr\Node; +use WapplerSystems\Meilisearch\System\Solr\SolrConnection; +use InvalidArgumentException; +use TYPO3\CMS\Core\Registry; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use function json_encode; + +/** + * ConnectionManager is responsible to create SolrConnection objects. + * + * @author Ingo Renner + */ +class ConnectionManager implements SingletonInterface +{ + + /** + * @var array + */ + protected static $connections = []; + + /** + * @var SystemLanguageRepository + */ + protected $systemLanguageRepository; + + /** + * @var PagesRepositoryAtExtSolr + */ + protected $pagesRepositoryAtExtSolr; + + /** + * @var SiteRepository + */ + protected $siteRepository; + + + /** + * @param SystemLanguageRepository $systemLanguageRepository + * @param PagesRepositoryAtExtSolr|null $pagesRepositoryAtExtSolr + * @param SiteRepository $siteRepository + */ + public function __construct( + SystemLanguageRepository $systemLanguageRepository = null, + PagesRepositoryAtExtSolr $pagesRepositoryAtExtSolr = null, + SiteRepository $siteRepository = null + ) + { + $this->systemLanguageRepository = $systemLanguageRepository ?? GeneralUtility::makeInstance(SystemLanguageRepository::class); + $this->siteRepository = $siteRepository ?? GeneralUtility::makeInstance(SiteRepository::class); + $this->pagesRepositoryAtExtSolr = $pagesRepositoryAtExtSolr ?? GeneralUtility::makeInstance(PagesRepositoryAtExtSolr::class); + } + + /** + * Creates a solr connection for read and write endpoints + * + * @param array $readNodeConfiguration + * @param array $writeNodeConfiguration + * @return SolrConnection|object + */ + public function getSolrConnectionForNodes(array $readNodeConfiguration, array $writeNodeConfiguration) + { + $connectionHash = md5(json_encode($readNodeConfiguration) . json_encode($writeNodeConfiguration)); + if (!isset(self::$connections[$connectionHash])) { + $readNode = Node::fromArray($readNodeConfiguration); + $writeNode = Node::fromArray($writeNodeConfiguration); + self::$connections[$connectionHash] = GeneralUtility::makeInstance(SolrConnection::class, $readNode, $writeNode); + } + return self::$connections[$connectionHash]; + } + + /** + * Creates a solr configuration from the configuration array and returns it. + * + * @param array $config The solr configuration array + * @return SolrConnection + */ + public function getConnectionFromConfiguration(array $config) + { + if(empty($config['read']) && !empty($config['solrHost'])) { + throw new InvalidArgumentException('Invalid registry data please re-initialize your solr connections'); + } + + return $this->getSolrConnectionForNodes($config['read'], $config['write']); + } + + /** + * Gets a Solr connection for a page ID. + * + * @param int $pageId A page ID. + * @param int $language The language ID to get the connection for as the path may differ. Optional, defaults to 0. + * @param string $mount Comma list of MountPoint parameters + * @return SolrConnection A solr connection. + * @throws NoSolrConnectionFoundException + */ + public function getConnectionByPageId($pageId, $language = 0, $mount = '') + { + try { + $site = $this->siteRepository->getSiteByPageId($pageId, $mount); + $this->throwExceptionOnInvalidSite($site, 'No site for pageId ' . $pageId); + $config = $site->getSolrConnectionConfiguration($language); + $solrConnection = $this->getConnectionFromConfiguration($config); + return $solrConnection; + } catch(InvalidArgumentException $e) { + $noSolrConnectionException = $this->buildNoConnectionExceptionForPageAndLanguage($pageId, $language); + throw $noSolrConnectionException; + } + } + + /** + * Gets a Solr connection for a root page ID. + * + * @param int $pageId A root page ID. + * @param int $language The language ID to get the connection for as the path may differ. Optional, defaults to 0. + * @return SolrConnection A solr connection. + * @throws NoSolrConnectionFoundException + */ + public function getConnectionByRootPageId($pageId, $language = 0) + { + try { + $site = $this->siteRepository->getSiteByRootPageId($pageId); + $this->throwExceptionOnInvalidSite($site, 'No site for pageId ' . $pageId); + $config = $site->getSolrConnectionConfiguration($language); + $solrConnection = $this->getConnectionFromConfiguration($config); + return $solrConnection; + } catch (InvalidArgumentException $e) { + /* @var NoSolrConnectionFoundException $noSolrConnectionException */ + $noSolrConnectionException = $this->buildNoConnectionExceptionForPageAndLanguage($pageId, $language); + throw $noSolrConnectionException; + } + } + + /** + * Gets all connections found. + * + * @return SolrConnection[] An array of initialized WapplerSystems\Meilisearch\System\Solr\SolrConnection connections + * @throws NoSolrConnectionFoundException + */ + public function getAllConnections() + { + $solrConnections = []; + foreach ($this->siteRepository->getAvailableSites() as $site) { + foreach ($site->getAllSolrConnectionConfigurations() as $solrConfiguration) { + $solrConnections[] = $this->getConnectionFromConfiguration($solrConfiguration); + } + } + + return $solrConnections; + } + + /** + * Gets all connections configured for a given site. + * + * @param Site $site A TYPO3 site + * @return SolrConnection[] An array of Solr connection objects (WapplerSystems\Meilisearch\System\Solr\SolrConnection) + * @throws NoSolrConnectionFoundException + */ + public function getConnectionsBySite(Site $site) + { + $connections = []; + + foreach ($site->getAllSolrConnectionConfigurations() as $languageId => $solrConnectionConfiguration) { + $connections[$languageId] = $this->getConnectionFromConfiguration($solrConnectionConfiguration); + } + + return $connections; + } + + /** + * Creates a human readable label from the connections' configuration. + * + * @param array $connection Connection configuration + * @return string Connection label + */ + protected function buildConnectionLabel(array $connection) + { + return $connection['rootPageTitle'] + . ' (pid: ' . $connection['rootPageUid'] + . ', language: ' . $this->systemLanguageRepository->findOneLanguageTitleByLanguageId($connection['language']) + . ') - Read node: ' + . $connection['read']['host'] . ':' + . $connection['read']['port'] + . $connection['read']['path'] + .' - Write node: ' + . $connection['write']['host'] . ':' + . $connection['write']['port'] + . $connection['write']['path']; + } + + /** + * @param $pageId + * @param $language + * @return NoSolrConnectionFoundException + */ + protected function buildNoConnectionExceptionForPageAndLanguage($pageId, $language): NoSolrConnectionFoundException + { + $message = 'Could not find a Solr connection for page [' . $pageId . '] and language [' . $language . '].'; + $noSolrConnectionException = $this->buildNoConnectionException($message); + + $noSolrConnectionException->setLanguageId($language); + return $noSolrConnectionException; + } + + /** + * Throws a no connection exception when no site was passed. + * + * @param Site|null $site + * @param $message + * @throws NoSolrConnectionFoundException + */ + protected function throwExceptionOnInvalidSite(?Site $site, string $message) + { + if (!is_null($site)) { + return; + } + + throw $this->buildNoConnectionException($message); + } + + /** + * Build a NoSolrConnectionFoundException with the passed message. + * @param string $message + * @return NoSolrConnectionFoundException + */ + protected function buildNoConnectionException(string $message): NoSolrConnectionFoundException + { + /* @var NoSolrConnectionFoundException $noSolrConnectionException */ + $noSolrConnectionException = GeneralUtility::makeInstance( + NoSolrConnectionFoundException::class, + /** @scrutinizer ignore-type */ + $message, + /** @scrutinizer ignore-type */ + 1575396474 + ); + return $noSolrConnectionException; + } +} diff --git a/Classes/ContentObject/Classification.php b/Classes/ContentObject/Classification.php new file mode 100644 index 0000000..bb82c58 --- /dev/null +++ b/Classes/ContentObject/Classification.php @@ -0,0 +1,117 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\Domain\Index\Classification\Classification as ClassificationItem; +use WapplerSystems\Meilisearch\Domain\Index\Classification\ClassificationService; +use InvalidArgumentException; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject; + +/** + * A content object (cObj) to classify content based on a configuration. + * + * Example usage: + * + * keywords = SOLR_CLASSIFICATION # supports stdWrap + * keywords { + * field = __solr_content # a comma separated field. instead of field you can also use "value" + * classes { + * 1 { + * patterns = smartphone, mobile, mobilephone # list of patterns that need to match to assign that class + * class = mobilephone # class that should be assigned when a pattern matches + * } + * } + * } + */ +class Classification extends AbstractContentObject +{ + const CONTENT_OBJECT_NAME = 'SOLR_CLASSIFICATION'; + + /** + * Executes the SOLR_CLASSIFICATION content object. + * + * Returns mapped classes when the field matches on of the configured patterns ... + * + * @inheritDoc + */ + public function render($conf = []) + { + + if (!is_array($conf['classes.'])) { + throw new InvalidArgumentException('No class configuration configured for SOLR_CLASSIFICATION object. Given configuration: ' . serialize($conf)); + } + + $configuredMappedClasses = $conf['classes.']; + unset($conf['classes.']); + + $data = ''; + if (isset($conf['value'])) { + $data = $conf['value']; + unset($conf['value']); + } + + if (!empty($conf)) { + $data = $this->cObj->stdWrap($data, $conf); + } + $classifications = $this->buildClassificationsFromConfiguration($configuredMappedClasses); + /** @var $classificationService ClassificationService */ + $classificationService = GeneralUtility::makeInstance(ClassificationService::class); + + return serialize($classificationService->getMatchingClassNames((string)$data, $classifications)); + } + + /** + * Builds an array of Classification objects from the passed classification configuration. + * + * @param array $configuredMappedClasses + * @return ClassificationItem[] + */ + protected function buildClassificationsFromConfiguration($configuredMappedClasses) : array + { + $classifications = []; + foreach ($configuredMappedClasses as $class) { + if ( (empty($class['patterns']) && empty($class['matchPatterns'])) || empty($class['class'])) { + throw new InvalidArgumentException('A class configuration in SOLR_CLASSIFCATION needs to have a pattern and a class configured. Given configuration: ' . serialize($class)); + } + + // @todo deprecate patterns configuration + $patterns = empty($class['patterns']) ? [] : GeneralUtility::trimExplode(',', $class['patterns']); + $matchPatterns = empty($class['matchPatterns']) ? [] : GeneralUtility::trimExplode(',', $class['matchPatterns']); + $matchPatterns = $matchPatterns + $patterns; + $unMatchPatters = empty($class['unmatchPatterns']) ? [] : GeneralUtility::trimExplode(',', $class['unmatchPatterns']); + + $className = $class['class']; + $classifications[] = GeneralUtility::makeInstance( + ClassificationItem::class, + /** @scrutinizer ignore-type */ $matchPatterns, + /** @scrutinizer ignore-type */ $unMatchPatters, + /** @scrutinizer ignore-type */ $className + ); + } + + return $classifications; + } +} diff --git a/Classes/ContentObject/Content.php b/Classes/ContentObject/Content.php new file mode 100644 index 0000000..e16e02b --- /dev/null +++ b/Classes/ContentObject/Content.php @@ -0,0 +1,80 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\HtmlContentExtractor; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +/** + * A content object (cObj) to clean a database field in a way so that it can be + * used to fill a Solr document's content field. + * + * @author Ingo Renner + */ +class Content extends AbstractContentObject +{ + const CONTENT_OBJECT_NAME = 'SOLR_CONTENT'; + + /** + * Executes the SOLR_CONTENT content object. + * + * Cleans content coming from a database field, removing HTML tags ... + * + * @inheritDoc + */ + public function render($conf = []) + { + $contentExtractor = GeneralUtility::makeInstance( + HtmlContentExtractor::class, + /** @scrutinizer ignore-type */ $this->getRawContent($this->cObj, $conf) + ); + + return $contentExtractor->getIndexableContent(); + } + + /** + * Gets the raw content as configured - a certain value or database field. + * + * @param ContentObjectRenderer $contentObject The original content object + * @param array $configuration content object configuration + * @return string The raw content + */ + protected function getRawContent($contentObject, $configuration) + { + $content = ''; + if (isset($configuration['value'])) { + $content = $configuration['value']; + unset($configuration['value']); + } + + if (!empty($configuration)) { + $content = $contentObject->stdWrap($content, $configuration); + } + + return $content; + } +} diff --git a/Classes/ContentObject/Multivalue.php b/Classes/ContentObject/Multivalue.php new file mode 100644 index 0000000..f90a81f --- /dev/null +++ b/Classes/ContentObject/Multivalue.php @@ -0,0 +1,92 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject; + +/** + * A content object (cObj) to turn comma separated strings into an array to be + * used in a multi value field in a Solr document. + * + * Example usage: + * + * keywords = SOLR_MULTIVALUE # supports stdWrap + * keywords { + * field = tags # a comma separated field. instead of field you can also use "value" + * separator = , # comma is the default value + * removeEmptyValues = 1 # a flag to remove empty strings from the list, on by default. + * removeDuplicateValues = 1 # a flag to remove duplicate strings from the list, off by default. + * } + * + * @author Ingo Renner + */ +class Multivalue extends AbstractContentObject +{ + const CONTENT_OBJECT_NAME = 'SOLR_MULTIVALUE'; + + /** + * Executes the SOLR_MULTIVALUE content object. + * + * Turns a list of values into an array that can then be used to fill + * multivalued fields in a Solr document. The array is returned in + * serialized form as content objects are expected to return strings. + * + * @inheritDoc + */ + public function render($conf = []) + { + $data = ''; + if (isset($conf['value'])) { + $data = $conf['value']; + unset($conf['value']); + } + + if (!empty($conf)) { + $data = $this->cObj->stdWrap($data, $conf); + } + + if (!array_key_exists('separator', $conf)) { + $conf['separator'] = ','; + } + + $removeEmptyValues = true; + if (isset($conf['removeEmptyValues']) && $conf['removeEmptyValues'] == 0) { + $removeEmptyValues = false; + } + + $listAsArray = GeneralUtility::trimExplode( + $conf['separator'], + $data, + $removeEmptyValues + ); + + if (!empty($conf['removeDuplicateValues'])) { + $listAsArray = array_unique($listAsArray); + } + + return serialize($listAsArray); + } +} diff --git a/Classes/ContentObject/Relation.php b/Classes/ContentObject/Relation.php new file mode 100644 index 0000000..eb39209 --- /dev/null +++ b/Classes/ContentObject/Relation.php @@ -0,0 +1,385 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\System\Language\FrontendOverlayService; +use WapplerSystems\Meilisearch\System\TCA\TCAService; +use WapplerSystems\Meilisearch\Util; +use Doctrine\DBAL\Driver\Statement; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Query\QueryBuilder; +use TYPO3\CMS\Core\Database\RelationHandler; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +/** + * A content object (cObj) to resolve relations between database records + * + * Configuration options: + * + * localField: the record's field to use to resolve relations + * foreignLabelField: Usually the label field to retrieve from the related records is determined automatically using TCA, using this option the desired field can be specified explicitly + * multiValue: whether to return related records suitable for a multi value field + * singleValueGlue: when not using multiValue, the related records need to be concatenated using a glue string, by default this is ", ". Using this option a custom glue can be specified. The custom value must be wrapped by pipe (|) characters. + * relationTableSortingField: field in an mm relation table to sort by, usually "sorting" + * enableRecursiveValueResolution: if the specified remote table's label field is a relation to another table, the value will be resolve by following the relation recursively. + * removeEmptyValues: Removes empty values when resolving relations, defaults to TRUE + * removeDuplicateValues: Removes duplicate values + * + * @author Ingo Renner + */ +class Relation extends AbstractContentObject +{ + const CONTENT_OBJECT_NAME = 'SOLR_RELATION'; + + /** + * Content object configuration + * + * @var array + */ + protected $configuration = []; + + /** + * @var TCAService + */ + protected $tcaService = null; + + /** + * @var FrontendOverlayService + */ + protected $frontendOverlayService = null; + + /** + * Relation constructor. + * @param TCAService|null $tcaService + * @param FrontendOverlayService|null $frontendOverlayService + */ + public function __construct(ContentObjectRenderer $cObj, TCAService $tcaService = null, FrontendOverlayService $frontendOverlayService = null) + { + $this->cObj = $cObj; + $this->configuration['enableRecursiveValueResolution'] = 1; + $this->configuration['removeEmptyValues'] = 1; + $this->tcaService = $tcaService ?? GeneralUtility::makeInstance(TCAService::class); + $this->frontendOverlayService = $frontendOverlayService ?? GeneralUtility::makeInstance(FrontendOverlayService::class); + } + + /** + * Executes the SOLR_RELATION content object. + * + * Resolves relations between records. Currently supported relations are + * TYPO3-style m:n relations. + * May resolve single value and multi value relations. + * + * @inheritDoc + */ + public function render($conf = []) + { + $this->configuration = array_merge($this->configuration, $conf); + + $relatedItems = $this->getRelatedItems($this->cObj); + + if (!empty($this->configuration['removeDuplicateValues'])) { + $relatedItems = array_unique($relatedItems); + } + + if (empty($conf['multiValue'])) { + // single value, need to concatenate related items + $singleValueGlue = !empty($conf['singleValueGlue']) ? trim($conf['singleValueGlue'], '|') : ', '; + $result = implode($singleValueGlue, $relatedItems); + } else { + // multi value, need to serialize as content objects must return strings + $result = serialize($relatedItems); + } + + return $result; + } + + /** + * Gets the related items of the current record's configured field. + * + * @param ContentObjectRenderer $parentContentObject parent content object + * @return array Array of related items, values already resolved from related records + */ + protected function getRelatedItems(ContentObjectRenderer $parentContentObject) + { + list($table, $uid) = explode(':', $parentContentObject->currentRecord); + $uid = (int) $uid; + $field = $this->configuration['localField']; + + if (!$this->tcaService->getHasConfigurationForField($table, $field)) { + return []; + } + + $overlayUid = $this->frontendOverlayService->getUidOfOverlay($table, $field, $uid); + $fieldTCA = $this->tcaService->getConfigurationForField($table, $field); + + if (isset($fieldTCA['config']['MM']) && trim($fieldTCA['config']['MM']) !== '') { + $relatedItems = $this->getRelatedItemsFromMMTable($table, $overlayUid, $fieldTCA); + } else { + $relatedItems = $this->getRelatedItemsFromForeignTable($table, $overlayUid, $fieldTCA, $parentContentObject); + } + + return $relatedItems; + } + + /** + * Gets the related items from a table using a n:m relation. + * + * @param string $localTableName Local table name + * @param int $localRecordUid Local record uid + * @param array $localFieldTca The local table's TCA + * @return array Array of related items, values already resolved from related records + */ + protected function getRelatedItemsFromMMTable($localTableName, $localRecordUid, array $localFieldTca) + { + $relatedItems = []; + $foreignTableName = $localFieldTca['config']['foreign_table']; + $foreignTableTca = $this->tcaService->getTableConfiguration($foreignTableName); + $foreignTableLabelField = $this->resolveForeignTableLabelField($foreignTableTca); + $mmTableName = $localFieldTca['config']['MM']; + + // Remove the first option of foreignLabelField for recursion + if (strpos($this->configuration['foreignLabelField'], '.') !== false) { + $foreignTableLabelFieldArr = explode('.', $this->configuration['foreignLabelField']); + unset($foreignTableLabelFieldArr[0]); + $this->configuration['foreignLabelField'] = implode('.', $foreignTableLabelFieldArr); + } + + $relationHandler = GeneralUtility::makeInstance(RelationHandler::class); + $relationHandler->start('', $foreignTableName, $mmTableName, $localRecordUid, $localTableName, $localFieldTca['config']); + $selectUids = $relationHandler->tableArray[$foreignTableName]; + if (!is_array($selectUids) || count($selectUids) <= 0) { + return $relatedItems; + } + + $relatedRecords = $this->getRelatedRecords($foreignTableName, ...$selectUids); + foreach ($relatedRecords as $record) { + if (isset($foreignTableTca['columns'][$foreignTableLabelField]['config']['foreign_table']) + && $this->configuration['enableRecursiveValueResolution'] + ) { + if (strpos($this->configuration['foreignLabelField'], '.') !== false) { + $foreignTableLabelFieldArr = explode('.', $this->configuration['foreignLabelField']); + unset($foreignTableLabelFieldArr[0]); + $this->configuration['foreignLabelField'] = implode('.', $foreignTableLabelFieldArr); + } + + $this->configuration['localField'] = $foreignTableLabelField; + + $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class); + $contentObject->start($record, $foreignTableName); + + return $this->getRelatedItems($contentObject); + } else { + if (Util::getLanguageUid() > 0) { + $record = $this->frontendOverlayService->getOverlay($foreignTableName, $record); + } + $relatedItems[] = $record[$foreignTableLabelField]; + } + } + + return $relatedItems; + } + + /** + * Resolves the field to use as the related item's label depending on TCA + * and TypoScript configuration + * + * @param array $foreignTableTca The foreign table's TCA + * @return string The field to use for the related item's label + */ + protected function resolveForeignTableLabelField(array $foreignTableTca) + { + $foreignTableLabelField = $foreignTableTca['ctrl']['label']; + + // when foreignLabelField is not enabled we can return directly + if (empty($this->configuration['foreignLabelField'])) { + return $foreignTableLabelField; + } + + if (strpos($this->configuration['foreignLabelField'], '.') !== false) { + list($foreignTableLabelField) = explode('.', $this->configuration['foreignLabelField'], 2); + } else { + $foreignTableLabelField = $this->configuration['foreignLabelField']; + } + + return $foreignTableLabelField; + } + + /** + * Gets the related items from a table using a 1:n relation. + * + * @param string $localTableName Local table name + * @param int $localRecordUid Local record uid + * @param array $localFieldTca The local table's TCA + * @param ContentObjectRenderer $parentContentObject parent content object + * @return array Array of related items, values already resolved from related records + */ + protected function getRelatedItemsFromForeignTable( + $localTableName, + $localRecordUid, + array $localFieldTca, + ContentObjectRenderer $parentContentObject + ) { + $relatedItems = []; + $foreignTableName = $localFieldTca['config']['foreign_table']; + $foreignTableTca = $this->tcaService->getTableConfiguration($foreignTableName); + $foreignTableLabelField = $this->resolveForeignTableLabelField($foreignTableTca); + + /** @var $relationHandler RelationHandler */ + $relationHandler = GeneralUtility::makeInstance(RelationHandler::class); + + $itemList = $parentContentObject->data[$this->configuration['localField']] ?? ''; + + $relationHandler->start($itemList, $foreignTableName, '', $localRecordUid, $localTableName, $localFieldTca['config']); + $selectUids = $relationHandler->tableArray[$foreignTableName]; + + if (!is_array($selectUids) || count($selectUids) <= 0) { + return $relatedItems; + } + + $relatedRecords = $this->getRelatedRecords($foreignTableName, ...$selectUids); + + foreach ($relatedRecords as $relatedRecord) { + $resolveRelatedValue = $this->resolveRelatedValue( + $relatedRecord, + $foreignTableTca, + $foreignTableLabelField, + $parentContentObject, + $foreignTableName + ); + if (!empty($resolveRelatedValue) || !$this->configuration['removeEmptyValues']) { + $relatedItems[] = $resolveRelatedValue; + } + } + + return $relatedItems; + } + + /** + * Resolves the value of the related field. If the related field's value is + * a relation itself, this method takes care of resolving it recursively. + * + * @param array $relatedRecord Related record as array + * @param array $foreignTableTca TCA of the related table + * @param string $foreignTableLabelField Field name of the foreign label field + * @param ContentObjectRenderer $parentContentObject cObject + * @param string $foreignTableName Related record table name + * + * @return string + */ + protected function resolveRelatedValue( + array $relatedRecord, + $foreignTableTca, + $foreignTableLabelField, + ContentObjectRenderer $parentContentObject, + $foreignTableName = '' + ) { + if (Util::getLanguageUid() > 0 && !empty($foreignTableName)) { + $relatedRecord = $this->frontendOverlayService->getOverlay($foreignTableName, $relatedRecord); + } + + $value = $relatedRecord[$foreignTableLabelField]; + + if ( + !empty($foreignTableName) + && isset($foreignTableTca['columns'][$foreignTableLabelField]['config']['foreign_table']) + && $this->configuration['enableRecursiveValueResolution'] + ) { + // backup + $backupRecord = $parentContentObject->data; + $backupConfiguration = $this->configuration; + + // adjust configuration for next level + $this->configuration['localField'] = $foreignTableLabelField; + $parentContentObject->data = $relatedRecord; + if (strpos($this->configuration['foreignLabelField'], '.') !== false) { + list(, $this->configuration['foreignLabelField']) = explode('.', + $this->configuration['foreignLabelField'], 2); + } else { + $this->configuration['foreignLabelField'] = ''; + } + + // recursion + $relatedItemsFromForeignTable = $this->getRelatedItemsFromForeignTable( + $foreignTableName, + $relatedRecord['uid'], + $foreignTableTca['columns'][$foreignTableLabelField], + $parentContentObject + ); + $value = array_pop($relatedItemsFromForeignTable); + + // restore + $this->configuration = $backupConfiguration; + $parentContentObject->data = $backupRecord; + } + + return $parentContentObject->stdWrap($value, $this->configuration); + } + + /** + * Return records via relation. + * + * @param string $foreignTable The table to fetch records from. + * @param int[] ...$uids The uids to fetch from table. + * @return array + */ + protected function getRelatedRecords($foreignTable, int ...$uids): array + { + /** @var QueryBuilder $queryBuilder */ + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($foreignTable); + $queryBuilder->select('*') + ->from($foreignTable) + ->where($queryBuilder->expr()->in('uid', $uids)); + if (isset($this->configuration['additionalWhereClause'])) { + $queryBuilder->andWhere($this->configuration['additionalWhereClause']); + } + $statement = $queryBuilder->execute(); + + return $this->sortByKeyInIN($statement, 'uid', ...$uids); + } + + /** + * Sorts the result set by key in array for IN values. + * Simulates MySqls ORDER BY FIELD(fieldname, COPY_OF_IN_FOR_WHERE) + * Example: SELECT * FROM a_table WHERE field_name IN (2, 3, 4) SORT BY FIELD(field_name, 2, 3, 4) + * + * + * @param Statement $statement + * @param string $columnName + * @param array $arrayWithValuesForIN + * @return array + */ + protected function sortByKeyInIN(Statement $statement, string $columnName, ...$arrayWithValuesForIN) : array + { + $records = []; + while ($record = $statement->fetch()) { + $indexNumber = array_search($record[$columnName], $arrayWithValuesForIN); + $records[$indexNumber] = $record; + } + ksort($records); + return $records; + } +} diff --git a/Classes/Controller/AbstractBaseController.php b/Classes/Controller/AbstractBaseController.php new file mode 100644 index 0000000..a0d4017 --- /dev/null +++ b/Classes/Controller/AbstractBaseController.php @@ -0,0 +1,276 @@ + + * @author Timo Hund + */ +abstract class AbstractBaseController extends ActionController +{ + /** + * @var ContentObjectRenderer + */ + private $contentObjectRenderer; + + /** + * @var TypoScriptFrontendController + */ + protected $typoScriptFrontendController; + + /** + * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface + */ + protected $configurationManager; + + /** + * @var SolrConfigurationManager + */ + private $solrConfigurationManager; + + /** + * The configuration is private if you need it please get it from the controllerContext. + * + * @var TypoScriptConfiguration + */ + protected $typoScriptConfiguration; + + /** + * @var \WapplerSystems\Meilisearch\Mvc\Controller\SolrControllerContext + */ + protected $controllerContext; + + /** + * @var \WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSetService + */ + protected $searchService; + + /** + * @var \WapplerSystems\Meilisearch\Domain\Search\SearchRequestBuilder + */ + protected $searchRequestBuilder; + + /** + * @var bool + */ + protected $resetConfigurationBeforeInitialize = true; + + /** + * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager + * @return void + */ + public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager) + { + $this->configurationManager = $configurationManager; + // @extensionScannerIgnoreLine + $this->contentObjectRenderer = $this->configurationManager->getContentObject(); + } + + /** + * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer + */ + public function setContentObjectRenderer($contentObjectRenderer) + { + $this->contentObjectRenderer = $contentObjectRenderer; + } + + /** + * @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer + */ + public function getContentObjectRenderer() + { + return $this->contentObjectRenderer; + } + + /** + * @param SolrConfigurationManager $configurationManager + */ + public function injectSolrConfigurationManager(SolrConfigurationManager $configurationManager) + { + $this->solrConfigurationManager = $configurationManager; + } + + /** + * @param boolean $resetConfigurationBeforeInitialize + */ + public function setResetConfigurationBeforeInitialize($resetConfigurationBeforeInitialize) + { + $this->resetConfigurationBeforeInitialize = $resetConfigurationBeforeInitialize; + } + + /** + * Initialize the controller context + * + * @return \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext ControllerContext to be passed to the view + * @api + */ + protected function buildControllerContext() + { + /** @var $controllerContext \WapplerSystems\Meilisearch\Mvc\Controller\SolrControllerContext */ + $controllerContext = $this->objectManager->get(SolrControllerContext::class); + $controllerContext->setRequest($this->request); + $controllerContext->setResponse($this->response); + if ($this->arguments !== null) { + $controllerContext->setArguments($this->arguments); + } + $controllerContext->setUriBuilder($this->uriBuilder); + + $controllerContext->setTypoScriptConfiguration($this->typoScriptConfiguration); + + return $controllerContext; + } + + /** + * Initialize action + */ + protected function initializeAction() + { + // Reset configuration (to reset flexform overrides) if resetting is enabled + if ($this->resetConfigurationBeforeInitialize) { + $this->solrConfigurationManager->reset(); + } + /** @var TypoScriptService $typoScriptService */ + $typoScriptService = $this->objectManager->get(TypoScriptService::class); + + // Merge settings done by typoscript with solrConfiguration plugin.tx_meilisearch (obsolete when part of ext:solr) + $frameWorkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK); + $pluginSettings = []; + foreach (['search', 'settings', 'suggest', 'statistics', 'logging', 'general', 'solr', 'view'] as $key) { + if (isset($frameWorkConfiguration[$key])) { + $pluginSettings[$key] = $frameWorkConfiguration[$key]; + } + } + + $this->typoScriptConfiguration = $this->solrConfigurationManager->getTypoScriptConfiguration(); + if ($pluginSettings !== []) { + $this->typoScriptConfiguration->mergeSolrConfiguration( + $typoScriptService->convertPlainArrayToTypoScriptArray($pluginSettings), + true, + false + ); + } + + $this->objectManager->get(ConfigurationService::class) + ->overrideConfigurationWithFlexFormSettings( + $this->contentObjectRenderer->data['pi_flexform'], + $this->typoScriptConfiguration + ); + + parent::initializeAction(); + $this->typoScriptFrontendController = $GLOBALS['TSFE']; + $this->initializeSettings(); + + if ($this->actionMethodName !== 'solrNotAvailableAction') { + $this->initializeSearch(); + } + } + + /** + * Inject settings of plugin.tx_meilisearch + * + * @return void + */ + protected function initializeSettings() + { + /** @var $typoScriptService TypoScriptService */ + $typoScriptService = $this->objectManager->get(TypoScriptService::class); + + // Make sure plugin.tx_meilisearch.settings are available in the view as {settings} + $this->settings = $typoScriptService->convertTypoScriptArrayToPlainArray( + $this->typoScriptConfiguration->getObjectByPathOrDefault('plugin.tx_meilisearch.settings.', []) + ); + } + + /** + * Initialize the Solr connection and + * test the connection through a ping + */ + protected function initializeSearch() + { + /** @var \WapplerSystems\Meilisearch\ConnectionManager $solrConnection */ + try { + $solrConnection = $this->objectManager->get(ConnectionManager::class)->getConnectionByPageId($this->typoScriptFrontendController->id, Util::getLanguageUid(), $this->typoScriptFrontendController->MP); + $search = $this->objectManager->get(Search::class, $solrConnection); + + $this->searchService = $this->objectManager->get( + SearchResultSetService::class, + /** @scrutinizer ignore-type */ $this->typoScriptConfiguration, + /** @scrutinizer ignore-type */ $search + ); + } catch (NoSolrConnectionFoundException $e) { + $this->handleSolrUnavailable(); + } + } + + /** + * @return SearchRequestBuilder + */ + protected function getSearchRequestBuilder() + { + if ($this->searchRequestBuilder === null) { + $this->searchRequestBuilder = GeneralUtility::makeInstance(SearchRequestBuilder::class, /** @scrutinizer ignore-type */ $this->typoScriptConfiguration); + } + + return $this->searchRequestBuilder; + } + + /** + * Called when the solr server is unavailable. + * + * @return void + */ + protected function handleSolrUnavailable() + { + if ($this->typoScriptConfiguration->getLoggingExceptions()) { + /** @var SolrLogManager $logger */ + $logger = GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__); + $logger->log(SolrLogManager::ERROR, 'Solr server is not available'); + } + } + + /** + * Emits signal for various actions + * + * @param string $className Name of the class containing the signal + * @param string $signalName Name of the signal slot + * @param array $signalArguments arguments for the signal slot + * + * @return array + */ + protected function emitActionSignal($className, $signalName, array $signalArguments) + { + return $this->signalSlotDispatcher->dispatch($className, $signalName, $signalArguments)[0]; + } +} diff --git a/Classes/Controller/Backend/PageModuleSummary.php b/Classes/Controller/Backend/PageModuleSummary.php new file mode 100644 index 0000000..725e82f --- /dev/null +++ b/Classes/Controller/Backend/PageModuleSummary.php @@ -0,0 +1,214 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Reflection\ObjectAccess; +use TYPO3\CMS\Fluid\View\StandaloneView; +use TYPO3\CMS\Core\Localization\LanguageService; +use TYPO3\CMS\Backend\View\PageLayoutView; + +/** + * Summary to display flexform settings in the page layout backend module. + * + * @author Ingo Renner + * @author Timo Hund + */ +class PageModuleSummary +{ + /** + * PageLayoutView + * + * @var PageLayoutView + */ + protected $pageLayoutView; + + /** + * @var array + */ + protected $pluginContentElement = []; + + /** + * @var array + */ + protected $flexformData = []; + + /** + * @var array + */ + protected $settings = []; + + /** + * Returns information about a plugin's flexform configuration + * + * @param array $parameters Parameters to the hook + * @return string Plugin configuration information + */ + public function getSummary(array $parameters) + { + $this->initialize($parameters['row'], $parameters['pObj']); + + $this->addTargetPage(); + $this->addSettingFromFlexForm('Filter', 'search.query.filter'); + $this->addSettingFromFlexForm('Sorting', 'search.query.sortBy'); + $this->addSettingFromFlexForm('Results per Page', 'search.results.resultsPerPage'); + $this->addSettingFromFlexForm('Boost Function', 'search.query.boostFunction'); + $this->addSettingFromFlexForm('Boost Query', 'search.query.boostQuery'); + $this->addSettingFromFlexForm('Tie Breaker', 'search.query.tieParameter'); + $this->addSettingFromFlexForm('Template', 'view.templateFiles.results'); + return $this->render(); + } + + /** + * @param array $contentElement + * @param PageLayoutView $pObj + */ + protected function initialize(array $contentElement, PageLayoutView $pObj) + { + $this->pageLayoutView = $pObj; + + /** @var $service \TYPO3\CMS\Core\Service\FlexFormService::class */ + $service = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Service\FlexFormService::class); + $this->flexformData = $service->convertFlexFormContentToArray($contentElement['pi_flexform']); + $this->pluginContentElement = $contentElement; + } + + /** + * Adds the target page to the settings. + */ + protected function addTargetPage() + { + $targetPageId = $this->getFieldFromFlexform('search.targetPage'); + if (!empty($targetPageId)) { + $page = BackendUtility::getRecord('pages', $targetPageId, 'title'); + $this->settings['Target Page'] = '[' . (int)$targetPageId . '] ' . $page['title']; + } + } + + /** + * @param string $settingName + * @param string $flexFormField + */ + protected function addSettingFromFlexForm($settingName, $flexFormField) + { + $value = $this->getFieldFromFlexform($flexFormField); + + if (is_array($value)) { + $value = $this->addSettingFromFlexFormArray($settingName, $value); + } + $this->addSettingIfNotEmpty($settingName, (string)$value); + } + + /** + * @param string $settingName + * @param array $values + * @return bool + */ + protected function addSettingFromFlexFormArray($settingName, $values) + { + foreach ($values as $item) { + if (!isset($item['field'])) { + continue; + } + $field = $item['field']; + + $label = $settingName . ' '; + $label .= isset($field['field']) ? $field['field'] : ''; + $fieldValue = isset($field['value']) ? $field['value'] : ''; + $this->addSettingIfNotEmpty($label, (string)$fieldValue); + } + } + + /** + * @param string $settingName + * @param string $value + */ + protected function addSettingIfNotEmpty($settingName, $value) + { + if (!empty($value)) { + $this->settings[$settingName] = $value; + } + } + + /** + * Gets a field's value from flexform configuration, will check if + * flexform configuration is available. + * + * @param string $path name of the field + * @return string if nothing found, value if found + */ + protected function getFieldFromFlexform($path) + { + return ObjectAccess::getPropertyPath($this->flexformData, $path); + } + + /** + * @return string + */ + protected function render() + { + /** @var $standaloneView StandaloneView */ + $standaloneView = GeneralUtility::makeInstance(StandaloneView::class); + $standaloneView->setTemplatePathAndFilename( + GeneralUtility::getFileAbsFileName('EXT:meilisearch/Resources/Private/Templates/Backend/PageModule/Summary.html') + ); + + $standaloneView->assignMultiple([ + 'pluginLabel' => $this->getPluginLabel(), + 'hidden' => $this->pluginContentElement['hidden'], + 'settings' => $this->settings, + ]); + return $standaloneView->render(); + } + + /** + * Returns the plugin label + * + * @return string + */ + protected function getPluginLabel() + { + $label = BackendUtility::getLabelFromItemListMerged($this->pluginContentElement['pid'], 'tt_content', 'list_type', $this->pluginContentElement['list_type']); + if (!empty($label)) { + $label = $this->getLanguageService()->sL($label); + } else { + $label = sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), $this->pluginContentElement['list_type']); + } + + return $this->pageLayoutView->linkEditContent(htmlspecialchars($label), $this->pluginContentElement); + } + + /** + * Returns the language service + * + * @return LanguageService + */ + protected function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/Classes/Controller/Backend/Search/AbstractModuleController.php b/Classes/Controller/Backend/Search/AbstractModuleController.php new file mode 100644 index 0000000..f48f424 --- /dev/null +++ b/Classes/Controller/Backend/Search/AbstractModuleController.php @@ -0,0 +1,328 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\ConnectionManager; +use WapplerSystems\Meilisearch\Domain\Site\SiteRepository; +use WapplerSystems\Meilisearch\Domain\Site\Site; +use WapplerSystems\Meilisearch\System\Solr\SolrConnection as SolrCoreConnection; +use WapplerSystems\Meilisearch\System\Mvc\Backend\Component\Exception\InvalidViewObjectNameException; +use WapplerSystems\Meilisearch\System\Mvc\Backend\Service\ModuleDataStorageService; +use TYPO3\CMS\Backend\Template\Components\Menu\Menu; +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Backend\View\BackendTemplateView; +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Messaging\AbstractMessage; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use TYPO3\CMS\Extbase\Mvc\View\NotFoundView; +use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; +use TYPO3\CMS\Extbase\Utility\LocalizationUtility; + +/** + * Abstract Module + * + * @property BackendTemplateView $view + */ +abstract class AbstractModuleController extends ActionController +{ + /** + * Backend Template Container + * + * @var string + */ + protected $defaultViewObjectName = BackendTemplateView::class; + + /** + * In the pagetree selected page UID + * + * @var int + */ + protected $selectedPageUID; + + /** + * Holds the requested page UID because the selected page uid, + * might be overwritten by the automatic site selection. + * + * @var int + */ + protected $requestedPageUID; + + /** + * @var Site + */ + protected $selectedSite; + + /** + * @var SiteRepository + */ + protected $siteRepository; + + /** + * @var SolrCoreConnection + */ + protected $selectedSolrCoreConnection; + + /** + * @var Menu + */ + protected $coreSelectorMenu = null; + + /** + * @var ConnectionManager + */ + protected $solrConnectionManager = null; + + /** + * @var ModuleDataStorageService + */ + protected $moduleDataStorageService = null; + + /** + * @param Site $selectedSite + */ + public function setSelectedSite(Site $selectedSite) + { + $this->selectedSite = $selectedSite; + } + + /** + * @param SiteRepository $siteRepository + */ + public function injectSiteRepository(SiteRepository $siteRepository) + { + $this->siteRepository = $siteRepository; + } + + /** + * Initializes the controller and sets needed vars. + */ + protected function initializeAction() + { + parent::initializeAction(); + $this->solrConnectionManager = GeneralUtility::makeInstance(ConnectionManager::class); + $this->moduleDataStorageService = GeneralUtility::makeInstance(ModuleDataStorageService::class); + + $this->selectedPageUID = (int)GeneralUtility::_GP('id'); + if ($this->request->hasArgument('id')) { + $this->selectedPageUID = (int)$this->request->getArgument('id'); + } + + $this->requestedPageUID = $this->selectedPageUID; + + if ($this->autoSelectFirstSiteAndRootPageWhenOnlyOneSiteIsAvailable()) { + return; + } + + if ($this->selectedPageUID < 1) { + return; + } + + try { + $this->selectedSite = $this->siteRepository->getSiteByPageId($this->selectedPageUID); + } catch (\InvalidArgumentException $exception) { + return; + } + } + + /** + * @return bool + * @throws \Exception + */ + protected function autoSelectFirstSiteAndRootPageWhenOnlyOneSiteIsAvailable(): bool + { + if (count($this->siteRepository->getAvailableSites()) == 1) { + $this->selectedSite = $this->siteRepository->getFirstAvailableSite(); + + // we only overwrite the selected pageUid when no id was passed + if ($this->selectedPageUID === 0) { + $this->selectedPageUID = $this->selectedSite->getRootPageId(); + } + return true; + } + + return false; + } + + /** + * Set up the doc header properly here + * + * @param ViewInterface $view + * @return void + */ + protected function initializeView(ViewInterface $view) + { + parent::initializeView($view); + $sites = $this->siteRepository->getAvailableSites(); + + $selectOtherPage = count($sites) > 0 || $this->selectedPageUID < 1; + $this->view->assign('showSelectOtherPage', $selectOtherPage); + $this->view->assign('pageUID', $this->selectedPageUID); + if ($view instanceof NotFoundView || $this->selectedPageUID < 1) { + return; + } + $this->view->getModuleTemplate()->addJavaScriptCode('mainJsFunctions', ' + top.fsMod.recentIds["searchbackend"] = ' . (int)$this->selectedPageUID . ';' + ); + if (null === $this->selectedSite) { + return; + } + + /* @var BackendUserAuthentication $beUser */ + $beUser = $GLOBALS['BE_USER']; + $permissionClause = $beUser->getPagePermsClause(1); + $pageRecord = BackendUtility::readPageAccess($this->selectedSite->getRootPageId(), $permissionClause); + + if (false === $pageRecord) { + throw new \InvalidArgumentException(vsprintf('There is something wrong with permissions for page "%s" for backend user "%s".', [$this->selectedSite->getRootPageId(), $beUser->user['username']]), 1496146317); + } + $this->view->getModuleTemplate()->getDocHeaderComponent()->setMetaInformation($pageRecord); + } + + /** + * Generates selector menu in backends doc header using selected page from page tree. + * + * @param string|null $uriToRedirectTo + */ + public function generateCoreSelectorMenuUsingPageTree(string $uriToRedirectTo = null) + { + if ($this->selectedPageUID < 1 || null === $this->selectedSite) { + return; + } + + if ($this->view instanceof NotFoundView) { + $this->initializeSelectedSolrCoreConnection(); + return; + } + + $this->generateCoreSelectorMenu($this->selectedSite, $uriToRedirectTo); + } + + /** + * Generates Core selector Menu for given Site. + * + * @param Site $site + * @param string|null $uriToRedirectTo + * @throws InvalidViewObjectNameException + */ + protected function generateCoreSelectorMenu(Site $site, string $uriToRedirectTo = null) + { + if (!$this->view instanceof BackendTemplateView) { + throw new InvalidViewObjectNameException(vsprintf( + 'The controller "%s" must use BackendTemplateView to be able to generate menu for backends docheader. \ + Please set `protected $defaultViewObjectName = BackendTemplateView::class;` field in your controller.', + [static::class]), 1493804179); + } + $this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue()); + + $this->coreSelectorMenu = $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->makeMenu(); + $this->coreSelectorMenu->setIdentifier('component_core_selector_menu'); + + if (!isset($uriToRedirectTo)) { + $uriToRedirectTo = $this->uriBuilder->reset()->uriFor(); + } + + $this->initializeSelectedSolrCoreConnection(); + $cores = $this->solrConnectionManager->getConnectionsBySite($site); + foreach ($cores as $core) { + $coreAdmin = $core->getAdminService(); + $menuItem = $this->coreSelectorMenu->makeMenuItem(); + $menuItem->setTitle($coreAdmin->getCorePath()); + $uri = $this->uriBuilder->reset()->uriFor('switchCore', + [ + 'corePath' => $coreAdmin->getCorePath(), + 'uriToRedirectTo' => $uriToRedirectTo + ] + ); + $menuItem->setHref($uri); + + if ($coreAdmin->getCorePath() == $this->selectedSolrCoreConnection->getAdminService()->getCorePath()) { + $menuItem->setActive(true); + } + $this->coreSelectorMenu->addMenuItem($menuItem); + } + + $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->addMenu($this->coreSelectorMenu); + } + + /** + * Switches used core. + * + * Note: Does not check availability of core in site. All this stuff is done in the generation step. + * + * @param string $corePath + * @param string $uriToRedirectTo + */ + public function switchCoreAction(string $corePath, string $uriToRedirectTo) + { + $moduleData = $this->moduleDataStorageService->loadModuleData(); + $moduleData->setCore($corePath); + + $this->moduleDataStorageService->persistModuleData($moduleData); + $message = LocalizationUtility::translate('coreselector_switched_successfully', 'solr', [$corePath]); + $this->addFlashMessage($message); + $this->redirectToUri($uriToRedirectTo); + } + + /** + * Initializes the solr core connection considerately to the components state. + * Uses and persists default core connection if persisted core in Site does not exist. + * + */ + private function initializeSelectedSolrCoreConnection() + { + $moduleData = $this->moduleDataStorageService->loadModuleData(); + + $solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite); + $currentSolrCorePath = $moduleData->getCore(); + if (empty($currentSolrCorePath)) { + $this->initializeFirstAvailableSolrCoreConnection($solrCoreConnections, $moduleData); + return; + } + foreach ($solrCoreConnections as $solrCoreConnection) { + if ($solrCoreConnection->getAdminService()->getCorePath() == $currentSolrCorePath) { + $this->selectedSolrCoreConnection = $solrCoreConnection; + } + } + if (!$this->selectedSolrCoreConnection instanceof SolrCoreConnection && count($solrCoreConnections) > 0) { + $this->initializeFirstAvailableSolrCoreConnection($solrCoreConnections, $moduleData); + $message = LocalizationUtility::translate('coreselector_switched_to_default_core', 'solr', [$currentSolrCorePath, $this->selectedSite->getLabel(), $this->selectedSolrCoreConnection->getAdminService()->getCorePath()]); + $this->addFlashMessage($message, '', AbstractMessage::NOTICE); + } + } + + /** + * @param SolrCoreConnection[] $solrCoreConnections + */ + private function initializeFirstAvailableSolrCoreConnection(array $solrCoreConnections, $moduleData) + { + if (empty($solrCoreConnections)) { + return; + } + $this->selectedSolrCoreConnection = $solrCoreConnections[0]; + $moduleData->setCore($this->selectedSolrCoreConnection->getAdminService()->getCorePath()); + $this->moduleDataStorageService->persistModuleData($moduleData); + } +} diff --git a/Classes/Controller/Backend/Search/CoreOptimizationModuleController.php b/Classes/Controller/Backend/Search/CoreOptimizationModuleController.php new file mode 100644 index 0000000..e70b028 --- /dev/null +++ b/Classes/Controller/Backend/Search/CoreOptimizationModuleController.php @@ -0,0 +1,386 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\Utility\ManagedResourcesUtility; +use TYPO3\CMS\Backend\Template\ModuleTemplate; +use TYPO3\CMS\Core\Messaging\FlashMessage; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; + +/** + * Manage Synonyms and Stop words in Backend Module + * @property \TYPO3\CMS\Extbase\Mvc\Web\Response $response + */ +class CoreOptimizationModuleController extends AbstractModuleController +{ + /** + * Set up the doc header properly here + * + * @param ViewInterface $view + * @return void + */ + protected function initializeView(ViewInterface $view) + { + parent::initializeView($view); + + $this->generateCoreSelectorMenuUsingPageTree(); + /* @var ModuleTemplate $module */ // holds the state of chosen tab + $module = $this->objectManager->get(ModuleTemplate::class); + $coreOptimizationTabs = $module->getDynamicTabMenu([], 'coreOptimization'); + $this->view->assign('tabs', $coreOptimizationTabs); + } + + /** + * Gets synonyms and stopwords for the currently selected core + * + * @return void + */ + public function indexAction() + { + if ($this->selectedSolrCoreConnection === null) { + $this->view->assign('can_not_proceed', true); + return; + } + + $synonyms = []; + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + $rawSynonyms = $coreAdmin->getSynonyms(); + foreach ($rawSynonyms as $baseWord => $synonymList) { + $synonyms[$baseWord] = implode(', ', $synonymList); + } + + $stopWords = $coreAdmin->getStopWords(); + $this->view->assignMultiple([ + 'synonyms' => $synonyms, + 'stopWords' => implode(PHP_EOL, $stopWords), + 'stopWordsCount' => count($stopWords) + ]); + } + + /** + * Add synonyms to selected core + * + * @param string $baseWord + * @param string $synonyms + * @param bool $overrideExisting + * @return void + */ + public function addSynonymsAction(string $baseWord, string $synonyms, $overrideExisting) + { + if (empty($baseWord) || empty($synonyms)) { + $this->addFlashMessage( + 'Please provide a base word and synonyms.', + 'Missing parameter', + FlashMessage::ERROR + ); + } else { + $baseWord = mb_strtolower($baseWord); + $synonyms = mb_strtolower($synonyms); + + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + if ($overrideExisting && $coreAdmin->getSynonyms($baseWord)) { + $coreAdmin->deleteSynonym($baseWord); + } + $coreAdmin->addSynonym($baseWord, GeneralUtility::trimExplode(',', $synonyms, true)); + $coreAdmin->reloadCore(); + + $this->addFlashMessage( + '"' . $synonyms . '" added as synonyms for base word "' . $baseWord . '"' + ); + } + + $this->redirect('index'); + } + + /** + * @param string $fileFormat + * @return void + */ + public function exportStopWordsAction($fileFormat = 'txt') + { + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + $this->exportFile( + implode(PHP_EOL, $coreAdmin->getStopWords()), + 'stopwords', + $fileFormat + ); + } + + /** + * Exports synonyms to a download file. + * + * @param string $fileFormat + * @return string + */ + public function exportSynonymsAction($fileFormat = 'txt') + { + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + $synonyms = $coreAdmin->getSynonyms(); + return $this->exportFile(ManagedResourcesUtility::exportSynonymsToTxt($synonyms), 'synonyms', $fileFormat); + } + + /** + * @param array $synonymFileUpload + * @param bool $overrideExisting + * @param bool $deleteSynonymsBefore + * @return void + */ + public function importSynonymListAction(array $synonymFileUpload, $overrideExisting, $deleteSynonymsBefore) + { + if ($deleteSynonymsBefore) { + $this->deleteAllSynonyms(); + } + + $fileLines = ManagedResourcesUtility::importSynonymsFromPlainTextContents($synonymFileUpload); + $synonymCount = 0; + + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + foreach ($fileLines as $baseWord => $synonyms) { + if (!isset($baseWord) || empty($synonyms)) { + continue; + } + $this->deleteExistingSynonym($overrideExisting, $deleteSynonymsBefore, $baseWord); + $coreAdmin->addSynonym($baseWord, $synonyms); + $synonymCount++; + } + + $coreAdmin->reloadCore(); + $this->addFlashMessage( + $synonymCount . ' synonyms imported.' + ); + $this->redirect('index'); + + } + + /** + * @param array $stopwordsFileUpload + * @param bool $replaceStopwords + * @return void + */ + public function importStopWordListAction(array $stopwordsFileUpload, $replaceStopwords) + { + $this->saveStopWordsAction( + ManagedResourcesUtility::importStopwordsFromPlainTextContents($stopwordsFileUpload), + $replaceStopwords + ); + } + + /** + * Delete complete synonym list + * + * @return void + */ + public function deleteAllSynonymsAction() + { + $allSynonymsCouldBeDeleted = $this->deleteAllSynonyms(); + + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + $reloadResponse = $coreAdmin->reloadCore(); + + if ($allSynonymsCouldBeDeleted + && $reloadResponse->getHttpStatus() == 200 + ) { + $this->addFlashMessage( + 'All synonym removed.' + ); + } else { + $this->addFlashMessage( + 'Failed to remove all synonyms.', + 'An error occurred', + FlashMessage::ERROR + ); + } + $this->redirect('index'); + } + + /** + * Deletes a synonym mapping by its base word. + * + * @param string $baseWord Synonym mapping base word + */ + public function deleteSynonymsAction($baseWord) + { + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + $deleteResponse = $coreAdmin->deleteSynonym($baseWord); + $reloadResponse = $coreAdmin->reloadCore(); + + if ($deleteResponse->getHttpStatus() == 200 + && $reloadResponse->getHttpStatus() == 200 + ) { + $this->addFlashMessage( + 'Synonym removed.' + ); + } else { + $this->addFlashMessage( + 'Failed to remove synonym.', + 'An error occurred', + FlashMessage::ERROR + ); + } + + $this->redirect('index'); + } + + /** + * Saves the edited stop word list to Solr + * + * @param string $stopWords + * @param bool $replaceStopwords + * @return void + */ + public function saveStopWordsAction(string $stopWords, $replaceStopwords = true) + { + // lowercase stopword before saving because terms get lowercased before stopword filtering + $newStopWords = mb_strtolower($stopWords); + $newStopWords = GeneralUtility::trimExplode("\n", $newStopWords, true); + + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + $oldStopWords = $coreAdmin->getStopWords(); + + if ($replaceStopwords) { + $removedStopWords = array_diff($oldStopWords, $newStopWords); + $wordsRemoved = $this->removeStopsWordsFromIndex($removedStopWords); + } else { + $wordsRemoved = true; + } + + $wordsAdded = true; + $addedStopWords = array_diff($newStopWords, $oldStopWords); + if (!empty($addedStopWords)) { + $wordsAddedResponse = $coreAdmin->addStopWords($addedStopWords); + $wordsAdded = ($wordsAddedResponse->getHttpStatus() == 200); + } + + $reloadResponse = $coreAdmin->reloadCore(); + if ($wordsRemoved && $wordsAdded && $reloadResponse->getHttpStatus() == 200) { + $this->addFlashMessage( + 'Stop Words Updated.' + ); + } + + $this->redirect('index'); + } + + /** + * @param string $content + * @param string $type + * @param string $fileExtension + * @return string + */ + protected function exportFile($content, $type = 'synonyms', $fileExtension = 'txt') : string + { + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + + $this->response->setHeader('Content-type', 'text/plain', true); + $this->response->setHeader('Cache-control', 'public', true); + $this->response->setHeader('Content-Description', 'File transfer', true); + $this->response->setHeader( + 'Content-disposition', + 'attachment; filename =' . $type . '_' . + $coreAdmin->getPrimaryEndpoint()->getCore() . '.' . $fileExtension, + true + ); + + $this->response->setContent($content); + $this->sendFileResponse(); + } + + /** + * This method send the headers and content and does an exit, since without the exit TYPO3 produces and error. + * @return void + */ + protected function sendFileResponse() + { + $this->response->sendHeaders(); + $this->response->send(); + $this->response->shutdown(); + + exit(); + } + + /** + * Delete complete synonym list form solr + * + * @return bool + */ + protected function deleteAllSynonyms() : bool + { + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + $synonyms = $coreAdmin->getSynonyms(); + $allSynonymsCouldBeDeleted = true; + + foreach ($synonyms as $baseWord => $synonym) { + $deleteResponse = $coreAdmin->deleteSynonym($baseWord); + $allSynonymsCouldBeDeleted = $allSynonymsCouldBeDeleted && $deleteResponse->getHttpStatus() == 200; + } + + return $allSynonymsCouldBeDeleted; + } + + /** + * @param $stopwordsToRemove + * @return bool + */ + protected function removeStopsWordsFromIndex($stopwordsToRemove) : bool + { + $wordsRemoved = true; + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + + foreach ($stopwordsToRemove as $word) { + $response = $coreAdmin->deleteStopWord($word); + if ($response->getHttpStatus() != 200) { + $wordsRemoved = false; + $this->addFlashMessage( + 'Failed to remove stop word "' . $word . '".', + 'An error occurred', + FlashMessage::ERROR + ); + break; + } + } + + return $wordsRemoved; + } + + /** + * Delete synonym entry if selceted before + * @param bool $overrideExisting + * @param bool $deleteSynonymsBefore + * @param string $baseWord + */ + protected function deleteExistingSynonym($overrideExisting, $deleteSynonymsBefore, $baseWord) + { + $coreAdmin = $this->selectedSolrCoreConnection->getAdminService(); + + if (!$deleteSynonymsBefore && + $overrideExisting && + $coreAdmin->getSynonyms($baseWord) + ) { + $coreAdmin->deleteSynonym($baseWord); + } + + } +} diff --git a/Classes/Controller/Backend/Search/IndexAdministrationModuleController.php b/Classes/Controller/Backend/Search/IndexAdministrationModuleController.php new file mode 100644 index 0000000..cf802bb --- /dev/null +++ b/Classes/Controller/Backend/Search/IndexAdministrationModuleController.php @@ -0,0 +1,195 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use WapplerSystems\Meilisearch\ConnectionManager; +use WapplerSystems\Meilisearch\IndexQueue\Queue; +use WapplerSystems\Meilisearch\System\Solr\SolrConnection; +use WapplerSystems\Meilisearch\Util; +use TYPO3\CMS\Backend\Routing\UriBuilder as BackendUriBuilder; +use TYPO3\CMS\Core\Messaging\FlashMessage; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Web\ReferringRequest; +use TYPO3\CMS\Extbase\Utility\LocalizationUtility; + +/** + * Index Administration Module + * + * @author Ingo Renner + */ +class IndexAdministrationModuleController extends AbstractModuleController +{ + + /** + * @var Queue + */ + protected $indexQueue; + + /** + * @var ConnectionManager + */ + protected $solrConnectionManager = null; + + /** + * @param ConnectionManager $solrConnectionManager + */ + public function setSolrConnectionManager(ConnectionManager $solrConnectionManager) + { + $this->solrConnectionManager = $solrConnectionManager; + } + + /** + * Initializes the controller before invoking an action method. + */ + protected function initializeAction() + { + parent::initializeAction(); + $this->indexQueue = GeneralUtility::makeInstance(Queue::class); + $this->solrConnectionManager = GeneralUtility::makeInstance(ConnectionManager::class); + } + + /** + * Index action, shows an overview of available index maintenance operations. + * + * @return void + */ + public function indexAction() + { + if ($this->selectedSite === null || empty($this->solrConnectionManager->getConnectionsBySite($this->selectedSite))) { + $this->view->assign('can_not_proceed', true); + } + } + + /** + * Empties the site's indexes. + * + * @return void + */ + public function emptyIndexAction() + { + $siteHash = $this->selectedSite->getSiteHash(); + + try { + $affectedCores = []; + $solrServers = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite); + foreach ($solrServers as $solrServer) { + $writeService = $solrServer->getWriteService(); + /* @var $solrServer SolrConnection */ + $writeService->deleteByQuery('siteHash:' . $siteHash); + $writeService->commit(false, false, false); + $affectedCores[] = $writeService->getPrimaryEndpoint()->getCore(); + } + $message = LocalizationUtility::translate('solr.backend.index_administration.index_emptied_all', 'Solr', [$this->selectedSite->getLabel(), implode(', ', $affectedCores)]); + $this->addFlashMessage($message); + } catch (\Exception $e) { + $this->addFlashMessage(LocalizationUtility::translate('solr.backend.index_administration.error.on_empty_index', 'Solr', [$e->__toString()]), '', FlashMessage::ERROR); + } + + $this->redirect('index'); + } + + /** + * Empties the Index Queue + * + * @return void + */ + public function clearIndexQueueAction() + { + $this->indexQueue->deleteItemsBySite($this->selectedSite); + $this->addFlashMessage( + LocalizationUtility::translate('solr.backend.index_administration.success.queue_emptied', 'Solr', + [$this->selectedSite->getLabel()]) + ); + $this->redirectToReferrerModule(); + } + + /** + * Reloads the site's Solr cores. + * + * @return void + */ + public function reloadIndexConfigurationAction() + { + $coresReloaded = true; + $reloadedCores = []; + $solrServers = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite); + + foreach ($solrServers as $solrServer) { + /* @var $solrServer SolrConnection */ + $coreAdmin = $solrServer->getAdminService(); + $coreReloaded = $coreAdmin->reloadCore()->getHttpStatus() === 200; + + $coreName = $coreAdmin->getPrimaryEndpoint()->getCore(); + if (!$coreReloaded) { + $coresReloaded = false; + + $this->addFlashMessage( + 'Failed to reload index configuration for core "' . $coreName . '"', + '', + FlashMessage::ERROR + ); + break; + } + + $reloadedCores[] = $coreName; + } + + if ($coresReloaded) { + $this->addFlashMessage( + 'Core configuration reloaded (' . implode(', ', $reloadedCores) . ').', + '', + FlashMessage::OK + ); + } + + $this->redirect('index'); + } + + /** + * Redirects to the referrer module index Action. + * + * Fluids