From 2c9e27b3b78b343b0b13af7a8f19382046d684ae Mon Sep 17 00:00:00 2001 From: Sven Wappler Date: Sat, 24 Apr 2021 04:44:44 +0200 Subject: [PATCH] first commit --- Classes/ConnectionManager.php | 47 +- .../Search/AbstractModuleController.php | 4 +- .../CoreOptimizationModuleController.php | 386 --- .../IndexAdministrationModuleController.php | 6 +- .../Search/IndexQueueModuleController.php | 2 +- .../Backend/Search/InfoModuleController.php | 62 +- Classes/Domain/Index/IndexService.php | 4 +- .../Queue/GarbageRemover/AbstractStrategy.php | 2 +- .../Builder.php | 103 +- .../Repository.php | 2 +- .../Domain/Search/Query/ExtractingQuery.php | 47 - Classes/Domain/Search/Query/Query.php | 47 - .../Result/Parser/DefaultResultParser.php | 2 +- .../ResultSet/Result/SearchResultBuilder.php | 2 +- .../ResultSet/SearchResultSetService.php | 2 +- Classes/Domain/Site/Site.php | 18 +- Classes/Domain/Site/SiteInterface.php | 11 +- Classes/Domain/Site/SiteRepository.php | 51 +- Classes/Domain/Site/Typo3ManagedSite.php | 17 +- Classes/Domain/Variants/VariantsProcessor.php | 2 +- Classes/IndexQueue/Indexer.php | 16 +- Classes/IndexQueue/PageIndexer.php | 14 +- Classes/Report/MeilisearchStatus.php | 4 +- .../System/Meilisearch/Document/Document.php | 8 +- .../Meilisearch/MeilisearchConnection.php | 60 +- .../Service/AbstractMeilisearchService.php | 337 +-- .../Service/MeilisearchAdminService.php | 176 +- .../Service/MeilisearchReadService.php | 22 - .../Service/MeilisearchWriteService.php | 1 - Classes/System/Util/SiteUtility.php | 72 +- Classes/Task/ReIndexTask.php | 2 +- Classes/Typo3PageIndexer.php | 2 +- ...MeilisearchFrontendTagBasedViewHelper.php} | 0 ...AbstractMeilisearchFrontendViewHelper.php} | 0 ...AbstractMeilisearchTagBasedViewHelper.php} | 0 ....php => AbstractMeilisearchViewHelper.php} | 0 Configuration/TCA/Overrides/sys_template.php | 6 +- .../setup.typoscript | 0 .../TypoScript/BootstrapCss/setup.txt | 2 - .../Meilisearch/constants.typoscript | 28 + .../{Solr => Meilisearch}/setup.typoscript | 79 +- .../TypoScript/OpenSearch/constants.txt | 2 - .../OpenSearch/constants.typoscript | 9 +- Configuration/TypoScript/OpenSearch/setup.txt | 2 - .../TypoScript/OpenSearch/setup.typoscript | 101 +- Configuration/TypoScript/Solr/constants.txt | 2 - .../TypoScript/Solr/constants.typoscript | 27 - Configuration/TypoScript/Solr/setup.txt | 2 - .../TypoScript/StyleSheets/setup.txt | 2 - .../TypoScript/StyleSheets/setup.typoscript | 4 +- .../Backend/Search/InfoModule/Index.html | 1 - .../Public/JavaScript/Bootstrap/bootstrap.js | 2377 ----------------- .../JavaScript/Bootstrap/bootstrap.min.js | 7 - Resources/Public/JavaScript/Bootstrap/npm.js | 13 - ext_tables.php | 15 - 55 files changed, 333 insertions(+), 3877 deletions(-) delete mode 100644 Classes/Controller/Backend/Search/CoreOptimizationModuleController.php rename Classes/Domain/Search/{ApacheSolrDocument => MeilisearchDocument}/Builder.php (71%) rename Classes/Domain/Search/{ApacheSolrDocument => MeilisearchDocument}/Repository.php (98%) delete mode 100644 Classes/Domain/Search/Query/ExtractingQuery.php delete mode 100644 Classes/Domain/Search/Query/Query.php rename Classes/ViewHelpers/{AbstractSolrFrontendTagBasedViewHelper.php => AbstractMeilisearchFrontendTagBasedViewHelper.php} (100%) rename Classes/ViewHelpers/{AbstractSolrFrontendViewHelper.php => AbstractMeilisearchFrontendViewHelper.php} (100%) rename Classes/ViewHelpers/{AbstractSolrTagBasedViewHelper.php => AbstractMeilisearchTagBasedViewHelper.php} (100%) rename Classes/ViewHelpers/{AbstractSolrViewHelper.php => AbstractMeilisearchViewHelper.php} (100%) rename Configuration/TypoScript/{BootstrapCss => BootstrapCSS}/setup.typoscript (100%) delete mode 100644 Configuration/TypoScript/BootstrapCss/setup.txt create mode 100644 Configuration/TypoScript/Meilisearch/constants.typoscript rename Configuration/TypoScript/{Solr => Meilisearch}/setup.typoscript (85%) delete mode 100644 Configuration/TypoScript/OpenSearch/constants.txt delete mode 100644 Configuration/TypoScript/OpenSearch/setup.txt delete mode 100644 Configuration/TypoScript/Solr/constants.txt delete mode 100644 Configuration/TypoScript/Solr/constants.typoscript delete mode 100644 Configuration/TypoScript/Solr/setup.txt delete mode 100644 Configuration/TypoScript/StyleSheets/setup.txt delete mode 100644 Resources/Public/JavaScript/Bootstrap/bootstrap.js delete mode 100644 Resources/Public/JavaScript/Bootstrap/bootstrap.min.js delete mode 100644 Resources/Public/JavaScript/Bootstrap/npm.js diff --git a/Classes/ConnectionManager.php b/Classes/ConnectionManager.php index b6dbc7f..797b365 100644 --- a/Classes/ConnectionManager.php +++ b/Classes/ConnectionManager.php @@ -1,4 +1,5 @@ systemLanguageRepository = $systemLanguageRepository ?? GeneralUtility::makeInstance(SystemLanguageRepository::class); - $this->siteRepository = $siteRepository ?? GeneralUtility::makeInstance(SiteRepository::class); + $this->siteRepository = $siteRepository ?? GeneralUtility::makeInstance(SiteRepository::class); $this->pagesRepositoryAtExtMeilisearch = $pagesRepositoryAtExtMeilisearch ?? GeneralUtility::makeInstance(PagesRepositoryAtExtMeilisearch::class); } /** * Creates a meilisearch connection for read and write endpoints * - * @param array $readNodeConfiguration - * @param array $writeNodeConfiguration + * @param array $siteConfiguration * @return MeilisearchConnection|object */ - public function getMeilisearchConnectionForNodes(array $readNodeConfiguration, array $writeNodeConfiguration) + public function getMeilisearchConnectionForNode(array $siteConfiguration) { - $connectionHash = md5(json_encode($readNodeConfiguration) . json_encode($writeNodeConfiguration)); + $connectionHash = md5(json_encode($siteConfiguration)); if (!isset(self::$connections[$connectionHash])) { - $readNode = $this->createClientFromArray($readNodeConfiguration); - $writeNode = $this->createClientFromArray($writeNodeConfiguration); - self::$connections[$connectionHash] = GeneralUtility::makeInstance(MeilisearchConnection::class, $readNode, $writeNode); + $node = $this->createClientFromArray($siteConfiguration); + self::$connections[$connectionHash] = GeneralUtility::makeInstance(MeilisearchConnection::class, $node, $siteConfiguration); } return self::$connections[$connectionHash]; } @@ -107,11 +106,11 @@ class ConnectionManager implements SingletonInterface */ public function getConnectionFromConfiguration(array $config) { - if(empty($config['read']) && !empty($config['meilisearchHost'])) { + if (empty($config) && !empty($config['meilisearchHost'])) { throw new InvalidArgumentException('Invalid registry data please re-initialize your meilisearch connections'); } - return $this->getMeilisearchConnectionForNodes($config['read'], $config['write']); + return $this->getMeilisearchConnectionForNode($config); } /** @@ -131,7 +130,7 @@ class ConnectionManager implements SingletonInterface $config = $site->getMeilisearchConnectionConfiguration($language); $meilisearchConnection = $this->getConnectionFromConfiguration($config); return $meilisearchConnection; - } catch(InvalidArgumentException $e) { + } catch (InvalidArgumentException $e) { $noMeilisearchConnectionException = $this->buildNoConnectionExceptionForPageAndLanguage($pageId, $language); throw $noMeilisearchConnectionException; } @@ -170,7 +169,7 @@ class ConnectionManager implements SingletonInterface { $meilisearchConnections = []; foreach ($this->siteRepository->getAvailableSites() as $site) { - foreach ($site->getAllMeilisearchConnectionConfigurations() as $meilisearchConfiguration) { + foreach ($site->getMeilisearchConnectionConfigurations() as $meilisearchConfiguration) { $meilisearchConnections[] = $this->getConnectionFromConfiguration($meilisearchConfiguration); } } @@ -182,18 +181,11 @@ class ConnectionManager implements SingletonInterface * Gets all connections configured for a given site. * * @param Site $site A TYPO3 site - * @return MeilisearchConnection[] An array of Meilisearch connection objects (WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchConnection) - * @throws NoMeilisearchConnectionFoundException + * @return MeilisearchConnection An array of Meilisearch connection objects (WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchConnection) */ - public function getConnectionsBySite(Site $site) + public function getConnectionBySite(Site $site) { - $connections = []; - - foreach ($site->getAllMeilisearchConnectionConfigurations() as $languageId => $meilisearchConnectionConfiguration) { - $connections[$languageId] = $this->getConnectionFromConfiguration($meilisearchConnectionConfiguration); - } - - return $connections; + return $this->getConnectionFromConfiguration($site->getMeilisearchConnectionConfiguration()); } /** @@ -211,7 +203,7 @@ class ConnectionManager implements SingletonInterface . $connection['read']['host'] . ':' . $connection['read']['port'] . $connection['read']['path'] - .' - Write node: ' + . ' - Write node: ' . $connection['write']['host'] . ':' . $connection['write']['port'] . $connection['write']['path']; @@ -266,8 +258,9 @@ class ConnectionManager implements SingletonInterface } - private function createClientFromArray(array $configuration) { - return new Client(($configuration['scheme'] ?? 'http') . '://'.$configuration['host'].':'.$configuration['port'], $configuration['apiKey'] ?? null, new \TYPO3\CMS\Core\Http\Client(\TYPO3\CMS\Core\Http\Client\GuzzleClientFactory::getClient())); + private function createClientFromArray(array $configuration) + { + return new Client(($configuration['scheme'] ?? 'http') . '://' . $configuration['host'] . ':' . $configuration['port'], $configuration['apiKey'] ?? null, new \TYPO3\CMS\Core\Http\Client(\TYPO3\CMS\Core\Http\Client\GuzzleClientFactory::getClient())); } diff --git a/Classes/Controller/Backend/Search/AbstractModuleController.php b/Classes/Controller/Backend/Search/AbstractModuleController.php index 0f3a5b6..5f3647d 100644 --- a/Classes/Controller/Backend/Search/AbstractModuleController.php +++ b/Classes/Controller/Backend/Search/AbstractModuleController.php @@ -245,7 +245,7 @@ abstract class AbstractModuleController extends ActionController } $this->initializeSelectedMeilisearchCoreConnection(); - $cores = $this->meilisearchConnectionManager->getConnectionsBySite($site); + $cores = $this->meilisearchConnectionManager->getConnectionBySite($site); foreach ($cores as $core) { $coreAdmin = $core->getAdminService(); $menuItem = $this->coreSelectorMenu->makeMenuItem(); @@ -295,7 +295,7 @@ abstract class AbstractModuleController extends ActionController { $moduleData = $this->moduleDataStorageService->loadModuleData(); - $meilisearchCoreConnections = $this->meilisearchConnectionManager->getConnectionsBySite($this->selectedSite); + $meilisearchCoreConnections = $this->meilisearchConnectionManager->getConnectionBySite($this->selectedSite); $currentMeilisearchCorePath = $moduleData->getCore(); if (empty($currentMeilisearchCorePath)) { $this->initializeFirstAvailableMeilisearchCoreConnection($meilisearchCoreConnections, $moduleData); diff --git a/Classes/Controller/Backend/Search/CoreOptimizationModuleController.php b/Classes/Controller/Backend/Search/CoreOptimizationModuleController.php deleted file mode 100644 index f9329b8..0000000 --- a/Classes/Controller/Backend/Search/CoreOptimizationModuleController.php +++ /dev/null @@ -1,386 +0,0 @@ - - * 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->selectedMeilisearchCoreConnection === null) { - $this->view->assign('can_not_proceed', true); - return; - } - - $synonyms = []; - $coreAdmin = $this->selectedMeilisearchCoreConnection->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->selectedMeilisearchCoreConnection->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->selectedMeilisearchCoreConnection->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->selectedMeilisearchCoreConnection->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->selectedMeilisearchCoreConnection->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->selectedMeilisearchCoreConnection->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->selectedMeilisearchCoreConnection->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 Meilisearch - * - * @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->selectedMeilisearchCoreConnection->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->selectedMeilisearchCoreConnection->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 meilisearch - * - * @return bool - */ - protected function deleteAllSynonyms() : bool - { - $coreAdmin = $this->selectedMeilisearchCoreConnection->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->selectedMeilisearchCoreConnection->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->selectedMeilisearchCoreConnection->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 index 5aec3dc..937e3d7 100644 --- a/Classes/Controller/Backend/Search/IndexAdministrationModuleController.php +++ b/Classes/Controller/Backend/Search/IndexAdministrationModuleController.php @@ -77,7 +77,7 @@ class IndexAdministrationModuleController extends AbstractModuleController */ public function indexAction() { - if ($this->selectedSite === null || empty($this->meilisearchConnectionManager->getConnectionsBySite($this->selectedSite))) { + if ($this->selectedSite === null || empty($this->meilisearchConnectionManager->getConnectionBySite($this->selectedSite))) { $this->view->assign('can_not_proceed', true); } } @@ -93,7 +93,7 @@ class IndexAdministrationModuleController extends AbstractModuleController try { $affectedCores = []; - $meilisearchServers = $this->meilisearchConnectionManager->getConnectionsBySite($this->selectedSite); + $meilisearchServers = $this->meilisearchConnectionManager->getConnectionBySite($this->selectedSite); foreach ($meilisearchServers as $meilisearchServer) { $writeService = $meilisearchServer->getWriteService(); /* @var $meilisearchServer MeilisearchConnection */ @@ -134,7 +134,7 @@ class IndexAdministrationModuleController extends AbstractModuleController { $coresReloaded = true; $reloadedCores = []; - $meilisearchServers = $this->meilisearchConnectionManager->getConnectionsBySite($this->selectedSite); + $meilisearchServers = $this->meilisearchConnectionManager->getConnectionBySite($this->selectedSite); foreach ($meilisearchServers as $meilisearchServer) { /* @var $meilisearchServer MeilisearchConnection */ diff --git a/Classes/Controller/Backend/Search/IndexQueueModuleController.php b/Classes/Controller/Backend/Search/IndexQueueModuleController.php index ce79572..c00e74d 100644 --- a/Classes/Controller/Backend/Search/IndexQueueModuleController.php +++ b/Classes/Controller/Backend/Search/IndexQueueModuleController.php @@ -114,7 +114,7 @@ class IndexQueueModuleController extends AbstractModuleController */ protected function canQueueSelectedSite() { - if ($this->selectedSite === null || empty($this->meilisearchConnectionManager->getConnectionsBySite($this->selectedSite))) { + if ($this->selectedSite === null || empty($this->meilisearchConnectionManager->getConnectionBySite($this->selectedSite))) { return false; } $enabledIndexQueueConfigurationNames = $this->selectedSite->getMeilisearchConfiguration()->getEnabledIndexQueueConfigurationNames(); diff --git a/Classes/Controller/Backend/Search/InfoModuleController.php b/Classes/Controller/Backend/Search/InfoModuleController.php index b6cbb57..d14700f 100644 --- a/Classes/Controller/Backend/Search/InfoModuleController.php +++ b/Classes/Controller/Backend/Search/InfoModuleController.php @@ -24,10 +24,11 @@ namespace WapplerSystems\Meilisearch\Controller\Backend\Search; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ +use TYPO3\CMS\Core\Utility\DebugUtility; use WapplerSystems\Meilisearch\Api; use WapplerSystems\Meilisearch\ConnectionManager; use WapplerSystems\Meilisearch\Domain\Search\Statistics\StatisticsRepository; -use WapplerSystems\Meilisearch\Domain\Search\ApacheMeilisearchDocument\Repository; +use WapplerSystems\Meilisearch\Domain\Search\MeilisearchDocument\Repository; use WapplerSystems\Meilisearch\System\Meilisearch\ResponseAdapter; use WapplerSystems\Meilisearch\System\Validator\Path; use TYPO3\CMS\Backend\Template\ModuleTemplate; @@ -93,8 +94,8 @@ class InfoModuleController extends AbstractModuleController $this->collectConnectionInfos(); $this->collectStatistics(); - $this->collectIndexFieldsInfo(); - $this->collectIndexInspectorInfo(); + //$this->collectIndexFieldsInfo(); + //$this->collectIndexInspectorInfo(); } /** @@ -122,30 +123,22 @@ class InfoModuleController extends AbstractModuleController $missingHosts = []; $invalidPaths = []; - /* @var Path $path */ - $path = GeneralUtility::makeInstance(Path::class); - $connections = $this->meilisearchConnectionManager->getConnectionsBySite($this->selectedSite); + $connection = $this->meilisearchConnectionManager->getConnectionBySite($this->selectedSite); - if (empty($connections)) { + if (empty($connection)) { $this->view->assign('can_not_proceed', true); return; } - foreach ($connections as $connection) { - $coreAdmin = $connection->getAdminService(); - $coreUrl = (string)$coreAdmin; + $coreAdmin = $connection->getAdminService(); - if ($coreAdmin->ping()) { - $connectedHosts[] = $coreUrl; - } else { - $missingHosts[] = $coreUrl; - } - - if (!$path->isValidMeilisearchPath($coreAdmin->getCorePath())) { - $invalidPaths[] = $coreAdmin->getCorePath(); - } + if ($coreAdmin->ping()) { + $connectedHosts[] = $coreAdmin; + } else { + $missingHosts[] = $coreAdmin; } + $this->view->assignMultiple([ 'site' => $this->selectedSite, 'apiKey' => Api::getApiKey(), @@ -204,36 +197,15 @@ class InfoModuleController extends AbstractModuleController { $indexFieldsInfoByCorePaths = []; - $meilisearchCoreConnections = $this->meilisearchConnectionManager->getConnectionsBySite($this->selectedSite); - foreach ($meilisearchCoreConnections as $meilisearchCoreConnection) { + $meilisearchCoreConnections = $this->meilisearchConnectionManager->getConnectionBySite($this->selectedSite); + foreach ($meilisearchCoreConnections as $i => $meilisearchCoreConnection) { $coreAdmin = $meilisearchCoreConnection->getAdminService(); - $indexFieldsInfo = [ - 'corePath' => $coreAdmin->getCorePath() - ]; if ($coreAdmin->ping()) { - $lukeData = $coreAdmin->getLukeMetaData(); - /* @var Registry $registry */ - $registry = GeneralUtility::makeInstance(Registry::class); - $limit = $registry->get('tx_meilisearch', 'luke.limit', 20000); - $limitNote = ''; - - if (isset($lukeData->index->numDocs) && $lukeData->index->numDocs > $limit) { - $limitNote = 'Too many terms'; - } elseif (isset($lukeData->index->numDocs)) { - $limitNote = 'Nothing indexed'; - // below limit, so we can get more data - // Note: we use 2 since 1 fails on Ubuntu Hardy. - $lukeData = $coreAdmin->getLukeMetaData(2); - } - - $fields = $this->getFields($lukeData, $limitNote); - $coreMetrics = $this->getCoreMetrics($lukeData, $fields); $indexFieldsInfo['noError'] = 'OK'; - $indexFieldsInfo['fields'] = $fields; - $indexFieldsInfo['coreMetrics'] = $coreMetrics; + $indexFieldsInfo['coreMetrics'] = 'dedede'; } else { $indexFieldsInfo['noError'] = null; @@ -243,7 +215,7 @@ class InfoModuleController extends AbstractModuleController FlashMessage::ERROR ); } - $indexFieldsInfoByCorePaths[$coreAdmin->getCorePath()] = $indexFieldsInfo; + $indexFieldsInfoByCorePaths[$i] = $indexFieldsInfo; } $this->view->assign('indexFieldsInfoByCorePaths', $indexFieldsInfoByCorePaths); } @@ -255,7 +227,7 @@ class InfoModuleController extends AbstractModuleController */ protected function collectIndexInspectorInfo() { - $meilisearchCoreConnections = $this->meilisearchConnectionManager->getConnectionsBySite($this->selectedSite); + $meilisearchCoreConnections = $this->meilisearchConnectionManager->getConnectionBySite($this->selectedSite); $documentsByCoreAndType = []; foreach ($meilisearchCoreConnections as $languageId => $meilisearchCoreConnection) { $coreAdmin = $meilisearchCoreConnection->getAdminService(); diff --git a/Classes/Domain/Index/IndexService.php b/Classes/Domain/Index/IndexService.php index 8e5e9bc..318cf2f 100644 --- a/Classes/Domain/Index/IndexService.php +++ b/Classes/Domain/Index/IndexService.php @@ -33,8 +33,6 @@ use WapplerSystems\Meilisearch\Domain\Site\Site; use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration; use WapplerSystems\Meilisearch\System\Logging\MeilisearchLogManager; use WapplerSystems\Meilisearch\Task\IndexQueueWorkerTask; -use Solarium\Exception\HttpException; -use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\SignalSlot\Dispatcher; @@ -140,7 +138,7 @@ class IndexService $this->emitSignal('afterIndexItems', [$itemsToIndex, $this->getContextTask(), $indexRunId]); if ($enableCommitsSetting && count($itemsToIndex) > 0) { - $meilisearchServers = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionsBySite($this->site); + $meilisearchServers = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionBySite($this->site); foreach ($meilisearchServers as $meilisearchServer) { try { $meilisearchServer->getWriteService()->commit(false, false, false); diff --git a/Classes/Domain/Index/Queue/GarbageRemover/AbstractStrategy.php b/Classes/Domain/Index/Queue/GarbageRemover/AbstractStrategy.php index 2f34a59..843d6b2 100644 --- a/Classes/Domain/Index/Queue/GarbageRemover/AbstractStrategy.php +++ b/Classes/Domain/Index/Queue/GarbageRemover/AbstractStrategy.php @@ -121,7 +121,7 @@ abstract class AbstractStrategy $enableCommitsSetting = $site->getMeilisearchConfiguration()->getEnableCommits(); $siteHash = $site->getSiteHash(); // a site can have multiple connections (cores / languages) - $meilisearchConnections = $this->connectionManager->getConnectionsBySite($site); + $meilisearchConnections = $this->connectionManager->getConnectionBySite($site); if ($language > 0) { $meilisearchConnections = [$language => $meilisearchConnections[$language]]; } diff --git a/Classes/Domain/Search/ApacheSolrDocument/Builder.php b/Classes/Domain/Search/MeilisearchDocument/Builder.php similarity index 71% rename from Classes/Domain/Search/ApacheSolrDocument/Builder.php rename to Classes/Domain/Search/MeilisearchDocument/Builder.php index e4f484f..25a0897 100644 --- a/Classes/Domain/Search/ApacheSolrDocument/Builder.php +++ b/Classes/Domain/Search/MeilisearchDocument/Builder.php @@ -1,5 +1,5 @@ getSiteByPageId($page->id); $pageRecord = $page->page; $accessGroups = $this->getDocumentIdGroups($pageAccessRootline); $documentId = $this->getPageDocumentId($page, $accessGroups, $mountPointParameter); - $document->setField('id', $documentId); - $document->setField('site', $site->getDomain()); - $document->setField('siteHash', $site->getSiteHash()); - $document->setField('appKey', 'EXT:meilisearch'); - $document->setField('type', 'pages'); + $document['id'] = $documentId; + $document['site'] = $site->getDomain(); + $document['siteHash'] = $site->getSiteHash(); + $document['appKey'] = 'EXT:meilisearch'; + $document['type'] = 'pages'; // system fields - $document->setField('uid', $page->id); - $document->setField('pid', $pageRecord['pid']); + $document['uid'] = $page->id; + $document['pid'] = $pageRecord['pid']; // variantId $variantId = $this->variantIdBuilder->buildFromTypeAndUid('pages', $page->id); - $document->setField('variantId', $variantId); + $document['variantId'] = $variantId; - $document->setField('typeNum', $page->type); - $document->setField('created', $pageRecord['crdate']); - $document->setField('changed', $pageRecord['SYS_LASTCHANGED']); + $document['typeNum'] = $page->type; + $document['created'] = $pageRecord['crdate']; + $document['changed'] = $pageRecord['SYS_LASTCHANGED']; $rootline = $this->getRootLineFieldValue($page->id, $mountPointParameter); - $document->setField('rootline', $rootline); + $document['rootline'] = $rootline; // access $this->addAccessField($document, $pageAccessRootline); @@ -104,14 +103,14 @@ class Builder // content // @extensionScannerIgnoreLine $contentExtractor = $this->getExtractorForPageContent($page->content); - $document->setField('title', $contentExtractor->getPageTitle()); - $document->setField('subTitle', $pageRecord['subtitle']); - $document->setField('navTitle', $pageRecord['nav_title']); - $document->setField('author', $pageRecord['author']); - $document->setField('description', $pageRecord['description']); - $document->setField('abstract', $pageRecord['abstract']); - $document->setField('content', $contentExtractor->getIndexableContent()); - $document->setField('url', $url); + $document['title'] = $contentExtractor->getPageTitle(); + $document['subTitle'] = $pageRecord['subtitle']; + $document['navTitle'] = $pageRecord['nav_title']; + $document['author'] = $pageRecord['author']; + $document['description'] = $pageRecord['description']; + $document['abstract'] = $pageRecord['abstract']; + $document['content'] = $contentExtractor->getIndexableContent(); + $document['url'] = $url; $this->addKeywordsField($document, $pageRecord); $this->addTagContentFields($document, $contentExtractor->getTagContent()); @@ -127,9 +126,9 @@ class Builder * @param string $type * @param int $rootPageUid * @param string $accessRootLine - * @return Document + * @return array */ - public function fromRecord(array $itemRecord, string $type, int $rootPageUid, string $accessRootLine): Document + public function fromRecord(array $itemRecord, string $type, int $rootPageUid, string $accessRootLine): array { /* @var $document Document */ $document = GeneralUtility::makeInstance(Document::class); @@ -139,36 +138,36 @@ class Builder $documentId = $this->getDocumentId($type, $site->getRootPageId(), $itemRecord['uid']); // required fields - $document->setField('id', $documentId); - $document->setField('type', $type); - $document->setField('appKey', 'EXT:meilisearch'); + $document['id'] = $documentId; + $document['type'] = $type; + $document['appKey'] = 'EXT:meilisearch'; // site, siteHash - $document->setField('site', $site->getDomain()); - $document->setField('siteHash', $site->getSiteHash()); + $document['site'] = $site->getDomain(); + $document['siteHash'] = $site->getSiteHash(); // uid, pid - $document->setField('uid', $itemRecord['uid']); - $document->setField('pid', $itemRecord['pid']); + $document['uid'] = $itemRecord['uid']; + $document['pid'] = $itemRecord['pid']; // variantId $variantId = $this->variantIdBuilder->buildFromTypeAndUid($type, $itemRecord['uid']); - $document->setField('variantId', $variantId); + $document['variantId'] = $variantId; // created, changed if (!empty($GLOBALS['TCA'][$type]['ctrl']['crdate'])) { - $document->setField('created', $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['crdate']]); + $document['created'] = $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['crdate']]; } if (!empty($GLOBALS['TCA'][$type]['ctrl']['tstamp'])) { - $document->setField('changed', $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['tstamp']]); + $document['changed'] = $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['tstamp']]; } // access, endtime - $document->setField('access', $accessRootLine); + $document['access'] = $accessRootLine; if (!empty($GLOBALS['TCA'][$type]['ctrl']['enablecolumns']['endtime']) && $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['enablecolumns']['endtime']] != 0 ) { - $document->setField('endtime', $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['enablecolumns']['endtime']]); + $document['endtime'] = $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['enablecolumns']['endtime']]; } return $document; @@ -255,37 +254,37 @@ class Builder /** * Adds the access field to the document if needed. * - * @param Document $document + * @param array $document * @param Rootline $pageAccessRootline */ - protected function addAccessField(Document $document, Rootline $pageAccessRootline) + protected function addAccessField(array &$document, Rootline $pageAccessRootline) { $access = (string)$pageAccessRootline; if (trim($access) !== '') { - $document->setField('access', $access); + $document['access'] = $access; } } /** * Adds the endtime field value to the Document. * - * @param Document $document + * @param array $document * @param array $pageRecord */ - protected function addEndtimeField(Document $document, $pageRecord) + protected function addEndtimeField(array &$document, $pageRecord) { if ($pageRecord['endtime']) { - $document->setField('endtime', $pageRecord['endtime']); + $document['endtime'] = $pageRecord['endtime']; } } /** * Adds keywords, multi valued. * - * @param Document $document + * @param array $document * @param array $pageRecord */ - protected function addKeywordsField(Document $document, $pageRecord) + protected function addKeywordsField(array &$document, $pageRecord) { if (!isset($pageRecord['keywords'])) { return; @@ -293,20 +292,20 @@ class Builder $keywords = array_unique(GeneralUtility::trimExplode(',', $pageRecord['keywords'], true)); foreach ($keywords as $keyword) { - $document->addField('keywords', $keyword); + $document['keywords'] = $keyword; } } /** * Add content from several tags like headers, anchors, ... * - * @param Document $document + * @param array $document * @param array $tagContent */ - protected function addTagContentFields(Document $document, $tagContent = []) + protected function addTagContentFields(array &$document, $tagContent = []) { foreach ($tagContent as $fieldName => $fieldValue) { - $document->setField($fieldName, $fieldValue); + $document[$fieldName] = $fieldValue; } } } diff --git a/Classes/Domain/Search/ApacheSolrDocument/Repository.php b/Classes/Domain/Search/MeilisearchDocument/Repository.php similarity index 98% rename from Classes/Domain/Search/ApacheSolrDocument/Repository.php rename to Classes/Domain/Search/MeilisearchDocument/Repository.php index 2bef3e6..8ac084b 100644 --- a/Classes/Domain/Search/ApacheSolrDocument/Repository.php +++ b/Classes/Domain/Search/MeilisearchDocument/Repository.php @@ -1,5 +1,5 @@ - * 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 Solarium\QueryType\Extract\Query as SolariumExtractQuery; - -/** - * Specialized query for content extraction using Meilisearch Cell - * - */ -class ExtractingQuery extends SolariumExtractQuery -{ - /** - * Constructor - * - * @param string $file Absolute path to the file to extract content and meta data from. - */ - public function __construct($file) - { - parent::__construct(); - $this->setFile($file); - $this->addParam('extractFormat', 'text'); - } - -} diff --git a/Classes/Domain/Search/Query/Query.php b/Classes/Domain/Search/Query/Query.php deleted file mode 100644 index 32962c9..0000000 --- a/Classes/Domain/Search/Query/Query.php +++ /dev/null @@ -1,47 +0,0 @@ - - * 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 Solarium\QueryType\Select\Query\Query as SolariumQuery; - -class Query extends SolariumQuery { - - /** - * Returns the query parameters that should be used. - * - * @return array - */ - public function getQueryParameters() { - return $this->getParams(); - } - - /** - * @return string - */ - public function __toString() - { - return $this->getQuery(); - } -} diff --git a/Classes/Domain/Search/ResultSet/Result/Parser/DefaultResultParser.php b/Classes/Domain/Search/ResultSet/Result/Parser/DefaultResultParser.php index 357b2b2..738e1f1 100644 --- a/Classes/Domain/Search/ResultSet/Result/Parser/DefaultResultParser.php +++ b/Classes/Domain/Search/ResultSet/Result/Parser/DefaultResultParser.php @@ -62,7 +62,7 @@ class DefaultResultParser extends AbstractResultParser { } foreach ($documents as $searchResult) { - $searchResultObject = $this->searchResultBuilder->fromApacheMeilisearchDocument($searchResult); + $searchResultObject = $this->searchResultBuilder->fromMeilisearchDocument($searchResult); $searchResults[] = $searchResultObject; } diff --git a/Classes/Domain/Search/ResultSet/Result/SearchResultBuilder.php b/Classes/Domain/Search/ResultSet/Result/SearchResultBuilder.php index e1330d4..378f03b 100644 --- a/Classes/Domain/Search/ResultSet/Result/SearchResultBuilder.php +++ b/Classes/Domain/Search/ResultSet/Result/SearchResultBuilder.php @@ -42,7 +42,7 @@ class SearchResultBuilder { * @throws \InvalidArgumentException * @return SearchResult */ - public function fromApacheMeilisearchDocument(Document $originalDocument) + public function fromMeilisearchDocument(Document $originalDocument) { $searchResultClassName = $this->getResultClassName(); diff --git a/Classes/Domain/Search/ResultSet/SearchResultSetService.php b/Classes/Domain/Search/ResultSet/SearchResultSetService.php index c5e881f..111e5c5 100644 --- a/Classes/Domain/Search/ResultSet/SearchResultSetService.php +++ b/Classes/Domain/Search/ResultSet/SearchResultSetService.php @@ -431,7 +431,7 @@ class SearchResultSetService throw new \UnexpectedValueException("Response did not contain a valid Document object"); } - return $this->searchResultBuilder->fromApacheMeilisearchDocument($resultDocument); + return $this->searchResultBuilder->fromMeilisearchDocument($resultDocument); } /** diff --git a/Classes/Domain/Site/Site.php b/Classes/Domain/Site/Site.php index 82f199b..9ce9be2 100644 --- a/Classes/Domain/Site/Site.php +++ b/Classes/Domain/Site/Site.php @@ -239,28 +239,14 @@ abstract class Site implements SiteInterface return $rootPageIds; } - /** - * @return array - * @throws NoMeilisearchConnectionFoundException - */ - public function getAllMeilisearchConnectionConfigurations(): array { - $configs = []; - foreach ($this->getAvailableLanguageIds() as $languageId) { - try { - $configs[$languageId] = $this->getMeilisearchConnectionConfiguration($languageId); - } catch (NoMeilisearchConnectionFoundException $e) {} - } - return $configs; - } public function isEnabled(): bool { - return !empty($this->getAllMeilisearchConnectionConfigurations()); + return !empty($this->getMeilisearchConnectionConfiguration()); } /** - * @param int $languageId * @return array */ - abstract function getMeilisearchConnectionConfiguration(int $language = 0): array; + abstract function getMeilisearchConnectionConfiguration(): array; } diff --git a/Classes/Domain/Site/SiteInterface.php b/Classes/Domain/Site/SiteInterface.php index d8ef4c6..e96edfd 100644 --- a/Classes/Domain/Site/SiteInterface.php +++ b/Classes/Domain/Site/SiteInterface.php @@ -110,19 +110,12 @@ interface SiteInterface */ public function getTitle(); - - /** - * @param int $language - * @return array - * @throws NoMeilisearchConnectionFoundException - */ - public function getMeilisearchConnectionConfiguration(int $language = 0): array; - /** * @return array * @throws NoMeilisearchConnectionFoundException */ - public function getAllMeilisearchConnectionConfigurations(): array; + public function getMeilisearchConnectionConfiguration(): array; + public function isEnabled(): bool; } diff --git a/Classes/Domain/Site/SiteRepository.php b/Classes/Domain/Site/SiteRepository.php index 29cab0e..068a5a1 100644 --- a/Classes/Domain/Site/SiteRepository.php +++ b/Classes/Domain/Site/SiteRepository.php @@ -85,7 +85,7 @@ class SiteRepository * @param TwoLevelCache|null $twoLevelCache * @param Registry|null $registry * @param SiteFinder|null $siteFinder - * @param ExtensionConfiguration| null + * @param ExtensionConfiguration|null */ public function __construct( RootPageResolver $rootPageResolver = null, @@ -230,8 +230,7 @@ class SiteRepository { /** @var $siteHashService SiteHashService */ $siteHashService = GeneralUtility::makeInstance(SiteHashService::class); - $siteHash = $siteHashService->getSiteHashForDomain($domain); - return $siteHash; + return $siteHashService->getSiteHashForDomain($domain); } /** @@ -272,37 +271,21 @@ class SiteRepository $domain = $typo3Site->getBase()->getHost(); $siteHash = $this->getSiteHashForDomain($domain); - $defaultLanguage = $typo3Site->getDefaultLanguage()->getLanguageId(); $pageRepository = GeneralUtility::makeInstance(PagesRepository::class); - $availableLanguageIds = array_map(function($language) { - return $language->getLanguageId(); - }, $typo3Site->getLanguages()); - $meilisearchConnectionConfigurations = []; + $meilisearchConnectionConfiguration = []; - foreach ($availableLanguageIds as $languageUid) { - $meilisearchEnabled = SiteUtility::getConnectionProperty($typo3Site, 'enabled', $languageUid, 'read', true); - if ($meilisearchEnabled) { - $meilisearchConnectionConfigurations[$languageUid] = [ - 'connectionKey' => $rootPageRecord['uid'] . '|' . $languageUid, - 'rootPageTitle' => $rootPageRecord['title'], - 'rootPageUid' => $rootPageRecord['uid'], - 'read' => [ - 'scheme' => SiteUtility::getConnectionProperty($typo3Site, 'scheme', $languageUid, 'read', 'http'), - 'host' => SiteUtility::getConnectionProperty($typo3Site, 'host', $languageUid, 'read', 'localhost'), - 'port' => (int)SiteUtility::getConnectionProperty($typo3Site, 'port', $languageUid, 'read', 7700), - 'apiKey' => SiteUtility::getConnectionProperty($typo3Site, 'apiKey', $languageUid, 'read', ''), - ], - 'write' => [ - 'scheme' => SiteUtility::getConnectionProperty($typo3Site, 'scheme', $languageUid, 'write', 'http'), - 'host' => SiteUtility::getConnectionProperty($typo3Site, 'host', $languageUid, 'write', 'localhost'), - 'port' => (int)SiteUtility::getConnectionProperty($typo3Site, 'port', $languageUid, 'write', 7700), - 'apiKey' => SiteUtility::getConnectionProperty($typo3Site, 'apiKey', $languageUid, 'write', ''), - ], - - 'language' => $languageUid - ]; - } + $meilisearchEnabled = SiteUtility::getConnectionProperty($typo3Site, 'enabled', true); + if ($meilisearchEnabled) { + $meilisearchConnectionConfiguration = [ + 'connectionKey' => $rootPageRecord['uid'] . '|', + 'rootPageTitle' => $rootPageRecord['title'], + 'rootPageUid' => $rootPageRecord['uid'], + 'scheme' => SiteUtility::getConnectionProperty($typo3Site, 'scheme', 'http'), + 'host' => SiteUtility::getConnectionProperty($typo3Site, 'host', 'localhost'), + 'port' => (int)SiteUtility::getConnectionProperty($typo3Site, 'port',7700), + 'apiKey' => SiteUtility::getConnectionProperty($typo3Site, 'apiKey', ''), + ]; } return GeneralUtility::makeInstance( @@ -318,11 +301,7 @@ class SiteRepository /** @scrutinizer ignore-type */ $pageRepository, /** @scrutinizer ignore-type */ - $defaultLanguage, - /** @scrutinizer ignore-type */ - $availableLanguageIds, - /** @scrutinizer ignore-type */ - $meilisearchConnectionConfigurations, + $meilisearchConnectionConfiguration, /** @scrutinizer ignore-type */ $typo3Site ); diff --git a/Classes/Domain/Site/Typo3ManagedSite.php b/Classes/Domain/Site/Typo3ManagedSite.php index 16868e9..cabfa7c 100644 --- a/Classes/Domain/Site/Typo3ManagedSite.php +++ b/Classes/Domain/Site/Typo3ManagedSite.php @@ -47,32 +47,29 @@ class Typo3ManagedSite extends Site /** * @var array */ - protected $meilisearchConnectionConfigurations; + protected $meilisearchConnectionConfiguration; public function __construct( TypoScriptConfiguration $configuration, - array $page, $domain, $siteHash, PagesRepository $pagesRepository = null, $defaultLanguageId = 0, $availableLanguageIds = [], array $meilisearchConnectionConfigurations = [], Typo3Site $typo3SiteObject = null) + array $page, $domain, $siteHash, PagesRepository $pagesRepository = null, array $meilisearchConnectionConfiguration = [], Typo3Site $typo3SiteObject = null) { $this->configuration = $configuration; $this->rootPage = $page; $this->domain = $domain; $this->siteHash = $siteHash; $this->pagesRepository = $pagesRepository ?? GeneralUtility::makeInstance(PagesRepository::class); - $this->defaultLanguageId = $defaultLanguageId; - $this->availableLanguageIds = $availableLanguageIds; - $this->meilisearchConnectionConfigurations = $meilisearchConnectionConfigurations; + $this->meilisearchConnectionConfiguration = $meilisearchConnectionConfiguration; $this->typo3SiteObject = $typo3SiteObject; } /** - * @param int $language * @return array * @throws NoMeilisearchConnectionFoundException */ - public function getMeilisearchConnectionConfiguration(int $language = 0): array + public function getMeilisearchConnectionConfiguration(): array { - if (!is_array($this->meilisearchConnectionConfigurations[$language])) { + if (!is_array($this->meilisearchConnectionConfiguration)) { /* @var $noMeilisearchConnectionException NoMeilisearchConnectionFoundException */ $noMeilisearchConnectionException = GeneralUtility::makeInstance( NoMeilisearchConnectionFoundException::class, @@ -80,12 +77,11 @@ class Typo3ManagedSite extends Site /** @scrutinizer ignore-type */ 1552491117 ); $noMeilisearchConnectionException->setRootPageId($this->getRootPageId()); - $noMeilisearchConnectionException->setLanguageId($language); throw $noMeilisearchConnectionException; } - return $this->meilisearchConnectionConfigurations[$language]; + return $this->meilisearchConnectionConfiguration; } /** @@ -97,4 +93,5 @@ class Typo3ManagedSite extends Site { return $this->typo3SiteObject; } + } diff --git a/Classes/Domain/Variants/VariantsProcessor.php b/Classes/Domain/Variants/VariantsProcessor.php index c396347..60aa220 100644 --- a/Classes/Domain/Variants/VariantsProcessor.php +++ b/Classes/Domain/Variants/VariantsProcessor.php @@ -114,7 +114,7 @@ class VariantsProcessor implements SearchResultSetProcessor $fields = get_object_vars($variantDocumentArray); $variantDocument = new SearchResult($fields); - $variantSearchResult = $this->resultBuilder->fromApacheMeilisearchDocument($variantDocument); + $variantSearchResult = $this->resultBuilder->fromMeilisearchDocument($variantDocument); $variantSearchResult->setIsVariant(true); $variantSearchResult->setVariantParent($resultDocument); diff --git a/Classes/IndexQueue/Indexer.php b/Classes/IndexQueue/Indexer.php index eef2398..a0d8d5b 100644 --- a/Classes/IndexQueue/Indexer.php +++ b/Classes/IndexQueue/Indexer.php @@ -25,7 +25,7 @@ namespace WapplerSystems\Meilisearch\IndexQueue; ***************************************************************/ use WapplerSystems\Meilisearch\ConnectionManager; -use WapplerSystems\Meilisearch\Domain\Search\ApacheMeilisearchDocument\Builder; +use WapplerSystems\Meilisearch\Domain\Search\MeilisearchDocument\Builder; use WapplerSystems\Meilisearch\FieldProcessor\Service; use WapplerSystems\Meilisearch\FrontendEnvironment; use WapplerSystems\Meilisearch\NoMeilisearchConnectionFoundException; @@ -37,7 +37,6 @@ use WapplerSystems\Meilisearch\System\Meilisearch\ResponseAdapter; use WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchConnection; use Exception; use RuntimeException; -use Solarium\Exception\HttpException; use TYPO3\CMS\Core\Context\LanguageAspectFactory; use TYPO3\CMS\Core\Error\Http\ServiceUnavailableException; use TYPO3\CMS\Core\Exception\SiteNotFoundException; @@ -146,7 +145,7 @@ class Indexer extends AbstractIndexer $this->type = $item->getType(); $this->setLogging($item); - $meilisearchConnections = $this->getMeilisearchConnectionsByItem($item); + $meilisearchConnections = $this->getMeilisearchConnectionByItem($item); foreach ($meilisearchConnections as $systemLanguageUid => $meilisearchConnection) { $this->meilisearch = $meilisearchConnection; @@ -504,7 +503,6 @@ class Indexer extends AbstractIndexer return $documents; } - // Initialization /** * Gets the Meilisearch connections applicable for an item. @@ -515,9 +513,8 @@ class Indexer extends AbstractIndexer * @param Item $item An index queue item * @return array An array of WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchConnection connections, the array's keys are the sys_language_uid of the language of the connection */ - protected function getMeilisearchConnectionsByItem(Item $item) + protected function getMeilisearchConnectionByItem(Item $item) { - $meilisearchConnections = []; $rootPageId = $item->getRootPageUid(); if ($item->getType() === 'pages') { @@ -528,11 +525,8 @@ class Indexer extends AbstractIndexer // Meilisearch configurations possible for this item $site = $item->getSite(); - $meilisearchConfigurationsBySite = $site->getAllMeilisearchConnectionConfigurations(); - $siteLanguages = []; - foreach ($meilisearchConfigurationsBySite as $meilisearchConfiguration) { - $siteLanguages[] = $meilisearchConfiguration['language']; - } + return $site->getMeilisearchConnectionConfiguration(); + $defaultLanguageUid = $this->getDefaultLanguageUid($item, $site->getRootPage(), $siteLanguages); $translationOverlays = $this->getTranslationOverlaysWithConfiguredSite((int)$pageId, $site, (array)$siteLanguages); diff --git a/Classes/IndexQueue/PageIndexer.php b/Classes/IndexQueue/PageIndexer.php index 5c65ec2..bff0da6 100644 --- a/Classes/IndexQueue/PageIndexer.php +++ b/Classes/IndexQueue/PageIndexer.php @@ -24,6 +24,7 @@ namespace WapplerSystems\Meilisearch\IndexQueue; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ +use TYPO3\CMS\Core\Utility\DebugUtility; use WapplerSystems\Meilisearch\Access\Rootline; use WapplerSystems\Meilisearch\Access\RootlineElement; use WapplerSystems\Meilisearch\Domain\Index\PageIndexer\Helper\UriBuilder\AbstractUriStrategy; @@ -57,8 +58,12 @@ class PageIndexer extends Indexer return false; } - $meilisearchConnections = $this->getMeilisearchConnectionsByItem($item); - foreach ($meilisearchConnections as $systemLanguageUid => $meilisearchConnection) { + //$meilisearchConnection = $this->getMeilisearchConnectionByItem($item); + + $site = $item->getSite(); + $languageUids = $site->getAvailableLanguageIds(); + + foreach ($languageUids as $systemLanguageUid) { $contentAccessGroups = $this->getAccessGroupsFromContent($item, $systemLanguageUid); if (empty($contentAccessGroups)) { @@ -108,9 +113,9 @@ class PageIndexer extends Indexer * @param Item $item An index queue item * @return array An array of WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchConnection connections, the array's keys are the sys_language_uid of the language of the connection */ - protected function getMeilisearchConnectionsByItem(Item $item) + protected function getMeilisearchConnectionByItem(Item $item) { - $meilisearchConnections = parent::getMeilisearchConnectionsByItem($item); + $meilisearchConnections = parent::getMeilisearchConnectionByItem($item); $page = $item->getRecord(); // may use \TYPO3\CMS\Core\Utility\GeneralUtility::hideIfDefaultLanguage($page['l18n_cfg']) with TYPO3 4.6 @@ -289,6 +294,7 @@ class PageIndexer extends Indexer */ protected function indexPage(Item $item, $language = 0, $userGroup = 0) { + DebugUtility::debug('dededede'); $accessRootline = $this->getAccessRootline($item, $language, $userGroup); $request = $this->buildBasePageIndexerRequest(); $request->setIndexQueueItem($item); diff --git a/Classes/Report/MeilisearchStatus.php b/Classes/Report/MeilisearchStatus.php index 02fceaa..cdda007 100644 --- a/Classes/Report/MeilisearchStatus.php +++ b/Classes/Report/MeilisearchStatus.php @@ -90,7 +90,7 @@ class MeilisearchStatus extends AbstractMeilisearchStatus { $reports = []; foreach ($this->siteRepository->getAvailableSites() as $site) { - foreach ($site->getAllMeilisearchConnectionConfigurations() as $meilisearchConfiguration) { + foreach ($site->getMeilisearchConnectionConfigurations() as $meilisearchConfiguration) { $reports[] = $this->getConnectionStatus($meilisearchConfiguration); } } @@ -110,7 +110,7 @@ class MeilisearchStatus extends AbstractMeilisearchStatus $this->responseStatus = Status::OK; $meilisearchAdmin = $this->connectionManager - ->getMeilisearchConnectionForNodes($meilisearchConnection['read'], $meilisearchConnection['write']) + ->getMeilisearchConnectionForNode($meilisearchConnection['read'], $meilisearchConnection['write']) ->getAdminService(); $meilisearchVersion = $this->checkMeilisearchVersion($meilisearchAdmin); diff --git a/Classes/System/Meilisearch/Document/Document.php b/Classes/System/Meilisearch/Document/Document.php index 41c7ecf..f70615e 100644 --- a/Classes/System/Meilisearch/Document/Document.php +++ b/Classes/System/Meilisearch/Document/Document.php @@ -15,14 +15,13 @@ namespace WapplerSystems\Meilisearch\System\Meilisearch\Document; */ use RuntimeException; -use Solarium\QueryType\Update\Query\Document as SolariumDocument; /** * Document representing the update query document * * @author Timo Hund */ -class Document extends SolariumDocument +class Document { /** * Magic call method used to emulate getters as used by the template engine. @@ -33,13 +32,12 @@ class Document extends SolariumDocument */ public function __call($name, $arguments) { - if (substr($name, 0, 3) == 'get') { + if (substr($name, 0, 3) === 'get') { $field = substr($name, 3); $field = strtolower($field[0]) . substr($field, 1); return $this->fields[$field] ?? null; - } else { - throw new RuntimeException('Call to undefined method. Supports magic getters only.', 1311006605); } + throw new RuntimeException('Call to undefined method. Supports magic getters only.', 1311006605); } /** diff --git a/Classes/System/Meilisearch/MeilisearchConnection.php b/Classes/System/Meilisearch/MeilisearchConnection.php index 47bfbf1..e8557cb 100644 --- a/Classes/System/Meilisearch/MeilisearchConnection.php +++ b/Classes/System/Meilisearch/MeilisearchConnection.php @@ -69,6 +69,11 @@ class MeilisearchConnection */ protected $configuration; + /** + * @var array + */ + protected $siteConfiguration; + /** * @var SynonymParser */ @@ -85,9 +90,9 @@ class MeilisearchConnection protected $schemaParser = null; /** - * @var Client[] + * @var Client */ - protected $nodes = []; + protected $client ; /** * @var MeilisearchLogManager @@ -104,15 +109,6 @@ class MeilisearchConnection */ protected $psr7Client; - /** - * @var RequestFactoryInterface - */ - protected $requestFactory; - - /** - * @var StreamFactoryInterface - */ - protected $streamFactory; /** * @var EventDispatcherInterface @@ -122,8 +118,8 @@ class MeilisearchConnection /** * Constructor * - * @param Client $readNode - * @param Client $writeNode + * @param Client $client + * @param array $siteConfiguration * @param ?TypoScriptConfiguration $configuration * @param ?SynonymParser $synonymParser * @param ?StopWordParser $stopWordParser @@ -138,8 +134,8 @@ class MeilisearchConnection * @throws NotFoundExceptionInterface */ public function __construct( - Client $readNode, - Client $writeNode, + Client $client, + array $siteConfiguration, TypoScriptConfiguration $configuration = null, SynonymParser $synonymParser = null, StopWordParser $stopWordParser = null, @@ -148,9 +144,8 @@ class MeilisearchConnection ClientInterface $psr7Client = null, EventDispatcherInterface $eventDispatcher = null ) { - $this->nodes['read'] = $readNode; - $this->nodes['write'] = $writeNode; - $this->nodes['admin'] = $writeNode; + $this->client = $client; + $this->siteConfiguration = $siteConfiguration; $this->configuration = $configuration ?? Util::getMeilisearchConfiguration(); $this->synonymParser = $synonymParser; $this->stopWordParser = $stopWordParser; @@ -160,14 +155,6 @@ class MeilisearchConnection $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::getContainer()->get(EventDispatcherInterface::class); } - /** - * @param string $key - * @return Client - */ - public function getNode(string $key): Client - { - return $this->nodes[$key]; - } /** * @return MeilisearchAdminService @@ -187,9 +174,7 @@ class MeilisearchConnection */ protected function buildAdminService(): MeilisearchAdminService { - $endpointKey = 'admin'; - $client = $this->getClient($endpointKey); - return GeneralUtility::makeInstance(MeilisearchAdminService::class, $client, $this->configuration, $this->logger, $this->synonymParser, $this->stopWordParser, $this->schemaParser); + return GeneralUtility::makeInstance(MeilisearchAdminService::class, $this, $this->client, $this->configuration, $this->logger, $this->synonymParser, $this->stopWordParser, $this->schemaParser); } /** @@ -210,9 +195,7 @@ class MeilisearchConnection */ protected function buildReadService(): MeilisearchReadService { - $endpointKey = 'read'; - $client = $this->getClient($endpointKey); - return GeneralUtility::makeInstance(MeilisearchReadService::class, $client); + return GeneralUtility::makeInstance(MeilisearchReadService::class, $this->client); } /** @@ -233,9 +216,7 @@ class MeilisearchConnection */ protected function buildWriteService(): MeilisearchWriteService { - $endpointKey = 'write'; - $client = $this->getClient($endpointKey); - return GeneralUtility::makeInstance(MeilisearchWriteService::class, $client); + return GeneralUtility::makeInstance(MeilisearchWriteService::class, $this->client); } @@ -247,4 +228,13 @@ class MeilisearchConnection { $this->clients[$endpointKey] = $client; } + + /** + * @return array + */ + public function getSiteConfiguration(): array + { + return $this->siteConfiguration; + } + } diff --git a/Classes/System/Meilisearch/Service/AbstractMeilisearchService.php b/Classes/System/Meilisearch/Service/AbstractMeilisearchService.php index 66c3a8a..0bea07d 100644 --- a/Classes/System/Meilisearch/Service/AbstractMeilisearchService.php +++ b/Classes/System/Meilisearch/Service/AbstractMeilisearchService.php @@ -24,27 +24,18 @@ namespace WapplerSystems\Meilisearch\System\Meilisearch\Service; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ -use WapplerSystems\Meilisearch\PingFailedException; +use MeiliSearch\Client; +use MeiliSearch\Exceptions\CommunicationException; use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration; use WapplerSystems\Meilisearch\System\Logging\MeilisearchLogManager; +use WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchConnection; use WapplerSystems\Meilisearch\System\Meilisearch\ResponseAdapter; use WapplerSystems\Meilisearch\Util; -use Solarium\Client; -use Solarium\Core\Client\Endpoint; -use Solarium\Core\Client\Request; -use Solarium\Core\Query\QueryInterface; -use Solarium\Exception\HttpException; -use TYPO3\CMS\Core\Http\Uri; use TYPO3\CMS\Core\Utility\GeneralUtility; abstract class AbstractMeilisearchService { - /** - * @var array - */ - protected static $pingCache = []; - /** * @var TypoScriptConfiguration */ @@ -60,26 +51,22 @@ abstract class AbstractMeilisearchService */ protected $client = null; + /** + * @var MeilisearchConnection + */ + protected $meilisearchConnection = null; + /** * MeilisearchReadService constructor. */ - public function __construct(Client $client, $typoScriptConfiguration = null, $logManager = null) + public function __construct(MeilisearchConnection $meilisearchConnection, Client $client, $typoScriptConfiguration = null, $logManager = null) { + $this->meilisearchConnection = $meilisearchConnection; $this->client = $client; $this->configuration = $typoScriptConfiguration ?? Util::getMeilisearchConfiguration(); $this->logger = $logManager ?? GeneralUtility::makeInstance(MeilisearchLogManager::class, /** @scrutinizer ignore-type */ __CLASS__); } - /** - * Returns the path to the core meilisearch path + core path. - * - * @return string - */ - public function getCorePath() - { - $endpoint = $this->getPrimaryEndpoint(); - return is_null($endpoint) ? '' : $endpoint->getPath() .'/'. $endpoint->getCore(); - } /** * Returns the Solarium client @@ -92,158 +79,28 @@ abstract class AbstractMeilisearchService } /** - * Return a valid http URL given this server's host, port and path and a provided servlet name - * - * @param string $servlet - * @param array $params - * @return string + * @return MeilisearchConnection */ - protected function _constructUrl($servlet, $params = []) + public function getMeilisearchConnection(): ?MeilisearchConnection { - $queryString = count($params) ? '?' . http_build_query($params, null, '&') : ''; - return $this->__toString() . $servlet . $queryString; + return $this->meilisearchConnection; } /** - * Creates a string representation of the Meilisearch connection. Specifically - * will return the Meilisearch URL. * - * @return string The Meilisearch URL. - * @TODO: Add support for API version 2 */ public function __toString() { - $endpoint = $this->getPrimaryEndpoint(); - if (!$endpoint instanceof Endpoint) { - return ''; - } - try { - return $endpoint->getCoreBaseUri(); - } catch (\Exception $exception) { - } - return $endpoint->getScheme(). '://' . $endpoint->getHost() . ':' . $endpoint->getPort() . $endpoint->getPath() . '/' . $endpoint->getCore() . '/'; + $siteConfiguration = $this->meilisearchConnection->getSiteConfiguration(); + $strConnection = $siteConfiguration['schema'].$siteConfiguration['host']; + + if (!$this->ping()) return $strConnection; + + return $strConnection . ', ' . implode(',',$this->client->version()); } - /** - * @return Endpoint|null - */ - public function getPrimaryEndpoint() - { - return is_array($this->client->getEndpoints()) ? reset($this->client->getEndpoints()) : null; - } - /** - * Central method for making a get operation against this Meilisearch Server - * - * @param string $url - * @return ResponseAdapter - */ - protected function _sendRawGet($url) - { - return $this->_sendRawRequest($url, Request::METHOD_GET); - } - - /** - * Central method for making a HTTP DELETE operation against the Meilisearch server - * - * @param string $url - * @return ResponseAdapter - */ - protected function _sendRawDelete($url) - { - return $this->_sendRawRequest($url, Request::METHOD_DELETE); - } - - /** - * Central method for making a post operation against this Meilisearch Server - * - * @param string $url - * @param string $rawPost - * @param string $contentType - * @return ResponseAdapter - */ - protected function _sendRawPost($url, $rawPost, $contentType = 'text/xml; charset=UTF-8') - { - $initializeRequest = function(Request $request) use ($rawPost, $contentType) { - $request->setRawData($rawPost); - $request->addHeader('Content-Type: ' . $contentType); - return $request; - }; - - return $this->_sendRawRequest($url, Request::METHOD_POST, $rawPost, $initializeRequest); - } - - /** - * Method that performs an http request with the solarium client. - * - * @param string $url - * @param string $method - * @param string $body - * @param ?\Closure $initializeRequest - * @return ResponseAdapter - */ - protected function _sendRawRequest( - string $url, - $method = Request::METHOD_GET, - $body = '', - \Closure $initializeRequest = null - ) { - $logSeverity = MeilisearchLogManager::INFO; - $exception = null; - $url = $this->reviseUrl($url); - try { - $request = $this->buildSolariumRequestFromUrl($url, $method); - if($initializeRequest !== null) { - $request = $initializeRequest($request); - } - $response = $this->executeRequest($request); - } catch (HttpException $exception) { - $logSeverity = MeilisearchLogManager::ERROR; - $response = new ResponseAdapter($exception->getBody(), $exception->getCode(), $exception->getMessage()); - } - - if ($this->configuration->getLoggingQueryRawPost() || $response->getHttpStatus() != 200) { - $message = 'Querying Meilisearch using '.$method; - $this->writeLog($logSeverity, $message, $url, $response, $exception, $body); - } - - return $response; - } - - /** - * Revise url - * - Resolve relative paths - * - * @param string $url - * @return string - */ - protected function reviseUrl(string $url): string - { - /* @var Uri $uri */ - $uri = GeneralUtility::makeInstance(Uri::class, $url); - - if ((string)$uri->getPath() === '') { - return $url; - } - - $path = trim($uri->getPath(), '/'); - $pathsCurrent = explode('/', $path); - $pathNew = []; - foreach ($pathsCurrent as $pathCurrent) { - if ($pathCurrent === '..') { - array_pop($pathNew); - continue; - } - if ($pathCurrent === '.') { - continue; - } - $pathNew[] = $pathCurrent; - } - - $uri = $uri->withPath(implode('/', $pathNew)); - return (string)$uri; - } /** * Build the log data and writes the message to the log @@ -281,90 +138,29 @@ abstract class AbstractMeilisearchService if (!empty($e)) { $logData['exception'] = $e->__toString(); return $logData; - } else { - // trigger data parsing - // @extensionScannerIgnoreLine - $meilisearchResponse->response; - $logData['response data'] = print_r($meilisearchResponse, true); - return $logData; } + // trigger data parsing + // @extensionScannerIgnoreLine + $meilisearchResponse->response; + $logData['response data'] = print_r($meilisearchResponse, true); + return $logData; + } + /** - * Call the /admin/ping servlet, can be used to quickly tell if a connection to the - * server is available. - * - * Simply overrides the MeilisearchPhpClient implementation, changing ping from a - * HEAD to a GET request, see http://forge.typo3.org/issues/44167 - * - * Also does not report the time, see https://forge.typo3.org/issues/64551 - * - * @param boolean $useCache indicates if the ping result should be cached in the instance or not - * @return bool TRUE if Meilisearch can be reached, FALSE if not + * @return bool */ - public function ping($useCache = true) + public function ping() { try { - $httpResponse = $this->performPingRequest($useCache); - } catch (HttpException $exception) { + $health = $this->client->health(); + } catch (CommunicationException $ex) { return false; } - - return ($httpResponse->getHttpStatus() === 200); + return is_array($health) && $health['status'] === 'available'; } - /** - * Call the /admin/ping servlet, can be used to get the runtime of a ping request. - * - * @param boolean $useCache indicates if the ping result should be cached in the instance or not - * @return double runtime in milliseconds - * @throws \WapplerSystems\Meilisearch\PingFailedException - */ - public function getPingRoundTripRuntime($useCache = true) - { - try { - $start = $this->getMilliseconds(); - $httpResponse = $this->performPingRequest($useCache); - $end = $this->getMilliseconds(); - } catch (HttpException $e) { - $message = 'Meilisearch ping failed with unexpected response code: ' . $e->getCode(); - /** @var $exception \WapplerSystems\Meilisearch\PingFailedException */ - $exception = GeneralUtility::makeInstance(PingFailedException::class, /** @scrutinizer ignore-type */ $message); - throw $exception; - } - - if ($httpResponse->getHttpStatus() !== 200) { - $message = 'Meilisearch ping failed with unexpected response code: ' . $httpResponse->getHttpStatus(); - /** @var $exception \WapplerSystems\Meilisearch\PingFailedException */ - $exception = GeneralUtility::makeInstance(PingFailedException::class, /** @scrutinizer ignore-type */ $message); - throw $exception; - } - - return $end - $start; - } - - /** - * Performs a ping request and returns the result. - * - * @param boolean $useCache indicates if the ping result should be cached in the instance or not - * @return ResponseAdapter - */ - protected function performPingRequest($useCache = true) - { - $cacheKey = (string)($this); - if ($useCache && isset(static::$pingCache[$cacheKey])) { - return static::$pingCache[$cacheKey]; - } - - $pingQuery = $this->client->createPing(); - $pingResult = $this->createAndExecuteRequest($pingQuery); - - if ($useCache) { - static::$pingCache[$cacheKey] = $pingResult; - } - - return $pingResult; - } /** * Returns the current time in milliseconds. @@ -376,76 +172,5 @@ abstract class AbstractMeilisearchService return GeneralUtility::milliseconds(); } - /** - * @param QueryInterface $query - * @return ResponseAdapter - */ - protected function createAndExecuteRequest(QueryInterface $query): ResponseAdapter - { - $request = $this->client->createRequest($query); - return $this->executeRequest($request); - } - /** - * @param $request - * @return ResponseAdapter - */ - protected function executeRequest($request): ResponseAdapter - { - $result = $this->client->executeRequest($request); - return new ResponseAdapter($result->getBody(), $result->getStatusCode(), $result->getStatusMessage()); - } - - /** - * Build the request for Solarium. - * - * Important: The endpoint already contains the API information. - * The internal Solarium will append the information including the core if set. - * - * @param string $url - * @param string $httpMethod - * @return Request - */ - protected function buildSolariumRequestFromUrl(string $url, $httpMethod = Request::METHOD_GET): Request - { - $params = []; - parse_str(parse_url($url, PHP_URL_QUERY), $params); - $request = new Request(); - $path = parse_url($url, PHP_URL_PATH); - $endpoint = $this->getPrimaryEndpoint(); - $api = $request->getApi() === Request::API_V1 ? 'meilisearch' : 'api'; - $coreBasePath = $endpoint->getPath() . '/' . $api . '/' . $endpoint->getCore() . '/'; - - $handler = $this->buildRelativePath($coreBasePath, $path); - $request->setMethod($httpMethod); - $request->setParams($params); - $request->setHandler($handler); - return $request; - } - - /** - * Build a relative path from base path to target path. - * Required since Solarium contains the core information - * - * @param string $basePath - * @param string $targetPath - * @return string - */ - protected function buildRelativePath(string $basePath, string $targetPath): string - { - $basePath = trim($basePath, '/'); - $targetPath = trim($targetPath, '/'); - $baseElements = explode('/', $basePath); - $targetElements = explode('/', $targetPath); - $targetSegment = array_pop($targetElements); - foreach ($baseElements as $i => $segment) { - if (isset($targetElements[$i]) && $segment === $targetElements[$i]) { - unset($baseElements[$i], $targetElements[$i]); - } else { - break; - } - } - $targetElements[] = $targetSegment; - return str_repeat('../', count($baseElements)) . implode('/', $targetElements); - } } diff --git a/Classes/System/Meilisearch/Service/MeilisearchAdminService.php b/Classes/System/Meilisearch/Service/MeilisearchAdminService.php index 980aa2d..2750982 100644 --- a/Classes/System/Meilisearch/Service/MeilisearchAdminService.php +++ b/Classes/System/Meilisearch/Service/MeilisearchAdminService.php @@ -24,14 +24,15 @@ namespace WapplerSystems\Meilisearch\System\Meilisearch\Service; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ +use MeiliSearch\Client; use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration; use WapplerSystems\Meilisearch\System\Logging\MeilisearchLogManager; +use WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchConnection; use WapplerSystems\Meilisearch\System\Meilisearch\Parser\SchemaParser; use WapplerSystems\Meilisearch\System\Meilisearch\Parser\StopWordParser; use WapplerSystems\Meilisearch\System\Meilisearch\Parser\SynonymParser; use WapplerSystems\Meilisearch\System\Meilisearch\ResponseAdapter; use WapplerSystems\Meilisearch\System\Meilisearch\Schema\Schema; -use Solarium\Client; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -39,30 +40,9 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; */ class MeilisearchAdminService extends AbstractMeilisearchService { - const PLUGINS_SERVLET = 'admin/plugins'; - const LUKE_SERVLET = 'admin/luke'; - const SYSTEM_SERVLET = 'admin/system'; - const CORES_SERVLET = '../admin/cores'; - const FILE_SERVLET = 'admin/file'; - const SCHEMA_SERVLET = 'schema'; - const SYNONYMS_SERVLET = 'schema/analysis/synonyms/'; - const STOPWORDS_SERVLET = 'schema/analysis/stopwords/'; - - - /** - * @var array - */ - protected $lukeData = []; protected $systemData = null; - protected $pluginsData = []; - - /** - * @var string|null - */ - protected $meilisearchconfigName; - /** * @var SchemaParser */ @@ -95,13 +75,16 @@ class MeilisearchAdminService extends AbstractMeilisearchService /** * Constructor * - * @param TypoScriptConfiguration $typoScriptConfiguration - * @param SynonymParser $synonymParser - * @param StopWordParser $stopWordParser - * @param SchemaParser $schemaParser - * @param MeilisearchLogManager $logManager + * @param MeilisearchConnection $meilisearchConnection + * @param Client $client + * @param TypoScriptConfiguration|null $typoScriptConfiguration + * @param MeilisearchLogManager|null $logManager + * @param SynonymParser|null $synonymParser + * @param StopWordParser|null $stopWordParser + * @param SchemaParser|null $schemaParser */ public function __construct( + MeilisearchConnection $meilisearchConnection, Client $client, TypoScriptConfiguration $typoScriptConfiguration = null, MeilisearchLogManager $logManager = null, @@ -110,71 +93,13 @@ class MeilisearchAdminService extends AbstractMeilisearchService SchemaParser $schemaParser = null ) { - parent::__construct($client, $typoScriptConfiguration); + parent::__construct($meilisearchConnection, $client, $typoScriptConfiguration); $this->synonymParser = $synonymParser ?? GeneralUtility::makeInstance(SynonymParser::class); $this->stopWordParser = $stopWordParser ?? GeneralUtility::makeInstance(StopWordParser::class); $this->schemaParser = $schemaParser ?? GeneralUtility::makeInstance(SchemaParser::class); } - /** - * Call the /admin/system servlet and retrieve system information about Meilisearch - * - * @return ResponseAdapter - */ - public function system() - { - return $this->_sendRawGet($this->_constructUrl(self::SYSTEM_SERVLET, ['wt' => 'json'])); - } - - /** - * Gets information about the plugins installed in Meilisearch - * - * @return array A nested array of plugin data. - */ - public function getPluginsInformation() - { - if (count($this->pluginsData) == 0) { - $url = $this->_constructUrl(self::PLUGINS_SERVLET, ['wt' => 'json']); - $pluginsInformation = $this->_sendRawGet($url); - - // access a random property to trigger response parsing - $pluginsInformation->responseHeader; - $this->pluginsData = $pluginsInformation; - } - - return $this->pluginsData; - } - - /** - * get field meta data for the index - * - * @param int $numberOfTerms Number of top terms to fetch for each field - * @return \stdClass - */ - public function getFieldsMetaData($numberOfTerms = 0) - { - return $this->getLukeMetaData($numberOfTerms)->fields; - } - - /** - * Retrieves meta data about the index from the luke request handler - * - * @param int $numberOfTerms Number of top terms to fetch for each field - * @return ResponseAdapter Index meta data - */ - public function getLukeMetaData($numberOfTerms = 0) - { - if (!isset($this->lukeData[$numberOfTerms])) { - $lukeUrl = $this->_constructUrl( - self::LUKE_SERVLET, ['numTerms' => $numberOfTerms, 'wt' => 'json', 'fl' => '*'] - ); - - $this->lukeData[$numberOfTerms] = $this->_sendRawGet($lukeUrl); - } - - return $this->lukeData[$numberOfTerms]; - } /** * Gets information about the Meilisearch server @@ -187,86 +112,13 @@ class MeilisearchAdminService extends AbstractMeilisearchService $systemInformation = $this->system(); // access a random property to trigger response parsing - $systemInformation->responseHeader; $this->systemData = $systemInformation; } return $this->systemData; } - /** - * Gets the name of the meilisearchconfig.xml file installed and in use on the Meilisearch - * server. - * - * @return string Name of the active meilisearchconfig.xml - */ - public function getMeilisearchconfigName() - { - if (is_null($this->meilisearchconfigName)) { - $meilisearchconfigXmlUrl = $this->_constructUrl(self::FILE_SERVLET, ['file' => 'meilisearchconfig.xml']); - $response = $this->_sendRawGet($meilisearchconfigXmlUrl); - $meilisearchconfigXml = simplexml_load_string($response->getRawResponse()); - if ($meilisearchconfigXml === false) { - throw new \InvalidArgumentException('No valid xml response from schema file: ' . $meilisearchconfigXmlUrl); - } - $this->meilisearchconfigName = (string)$meilisearchconfigXml->attributes()->name; - } - return $this->meilisearchconfigName; - } - - /** - * Gets the Meilisearch server's version number. - * - * @return string Meilisearch version number - */ - public function getMeilisearchServerVersion() - { - $systemInformation = $this->getSystemInformation(); - // don't know why $systemInformation->lucene->meilisearch-spec-version won't work - $luceneInformation = (array)$systemInformation->lucene; - return $luceneInformation['meilisearch-spec-version']; - } - - /** - * Reloads the current core - * - * @return ResponseAdapter - */ - public function reloadCore() - { - $response = $this->reloadCoreByName($this->getPrimaryEndpoint()->getCore()); - return $response; - } - - /** - * Reloads a core of the connection by a given corename. - * - * @param string $coreName - * @return ResponseAdapter - */ - public function reloadCoreByName($coreName) - { - $coreAdminReloadUrl = $this->_constructUrl(self::CORES_SERVLET) . '?action=reload&core=' . $coreName; - $response = $this->_sendRawGet($coreAdminReloadUrl); - return $response; - } - - /** - * Get the configured schema for the current core. - * - * @return Schema - */ - public function getSchema() - { - if ($this->schema !== null) { - return $this->schema; - } - $response = $this->_sendRawGet($this->_constructUrl(self::SCHEMA_SERVLET)); - - $this->schema = $this->schemaParser->parseJson($response->getRawResponse()); - return $this->schema; - } /** * Get currently configured synonyms @@ -300,8 +152,7 @@ class MeilisearchAdminService extends AbstractMeilisearchService { $this->initializeSynonymsUrl(); $json = $this->synonymParser->toJson($baseWord, $synonyms); - $response = $this->_sendRawPost($this->_synonymsUrl, $json, 'application/json'); - return $response; + return $this->_sendRawPost($this->_synonymsUrl, $json, 'application/json'); } /** @@ -318,8 +169,7 @@ class MeilisearchAdminService extends AbstractMeilisearchService throw new \InvalidArgumentException('Must provide base word.'); } - $response = $this->_sendRawDelete($this->_synonymsUrl . '/' . urlencode($baseWord)); - return $response; + return $this->_sendRawDelete($this->_synonymsUrl . '/' . urlencode($baseWord)); } /** diff --git a/Classes/System/Meilisearch/Service/MeilisearchReadService.php b/Classes/System/Meilisearch/Service/MeilisearchReadService.php index e95d91c..ec20118 100644 --- a/Classes/System/Meilisearch/Service/MeilisearchReadService.php +++ b/Classes/System/Meilisearch/Service/MeilisearchReadService.php @@ -24,12 +24,10 @@ namespace WapplerSystems\Meilisearch\System\Meilisearch\Service; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ -use WapplerSystems\Meilisearch\Domain\Search\Query\Query; use WapplerSystems\Meilisearch\System\Meilisearch\ResponseAdapter; use WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchCommunicationException; use WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchInternalServerErrorException; use WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchUnavailableException; -use Solarium\Exception\HttpException; /** * Class MeilisearchReadService @@ -47,26 +45,6 @@ class MeilisearchReadService extends AbstractMeilisearchService */ protected $responseCache = null; - /** - * Performs a search. - * - * @param Query $query - * @return ResponseAdapter Meilisearch response - * @throws \RuntimeException if Meilisearch returns a HTTP status code other than 200 - */ - public function search($query) - { - try { - $request = $this->client->createRequest($query); - $response = $this->executeRequest($request); - $this->hasSearched = true; - $this->responseCache = $response; - } catch (HttpException $e) { - $this->handleErrorResponses($e); - } - return $response; - } - /** * Returns whether a search has been executed or not. * diff --git a/Classes/System/Meilisearch/Service/MeilisearchWriteService.php b/Classes/System/Meilisearch/Service/MeilisearchWriteService.php index 060b6f7..83d556b 100644 --- a/Classes/System/Meilisearch/Service/MeilisearchWriteService.php +++ b/Classes/System/Meilisearch/Service/MeilisearchWriteService.php @@ -26,7 +26,6 @@ namespace WapplerSystems\Meilisearch\System\Meilisearch\Service; use WapplerSystems\Meilisearch\System\Logging\MeilisearchLogManager; use WapplerSystems\Meilisearch\System\Meilisearch\ResponseAdapter; -use Solarium\QueryType\Extract\Query; /** * Class MeilisearchWriteService diff --git a/Classes/System/Util/SiteUtility.php b/Classes/System/Util/SiteUtility.php index 0ac4cf0..096fdbc 100644 --- a/Classes/System/Util/SiteUtility.php +++ b/Classes/System/Util/SiteUtility.php @@ -58,30 +58,16 @@ class SiteUtility } /** - * This method is used to retrieve the connection configuration from the TYPO3 site configuration. * - * Note: Language context properties have precedence over global settings. - * - * The configuration is done in the globals configuration of a site, and be extended in the language specific configuration - * of a site. - * - * Typically everything except the core name is configured on the global level and the core name differs for each language. - * - * In addition every property can be defined for the ```read``` and ```write``` scope. - * - * The convention for property keys is "meilisearch_{propertyName}_{scope}". With the configuration "meilisearch_host_read" you define the host - * for the meilisearch read connection. * * @param Site $typo3Site * @param string $property - * @param int $languageId - * @param string $scope * @param string $defaultValue * @return string */ - public static function getConnectionProperty(Site $typo3Site, string $property, int $languageId, string $scope, string $defaultValue = null): string + public static function getConnectionProperty(Site $typo3Site, string $property, string $defaultValue = null): string { - $value = self::getConnectionPropertyOrFallback($typo3Site, $property, $languageId, $scope); + $value = self::getConnectionPropertyOrFallback($typo3Site, $property); if ($value === null) { return $defaultValue; } @@ -94,72 +80,31 @@ class SiteUtility * * @param Site $typo3Site * @param string $property - * @param int $languageId - * @param string $scope * @return mixed */ - protected static function getConnectionPropertyOrFallback(Site $typo3Site, string $property, int $languageId, string $scope) + protected static function getConnectionPropertyOrFallback(Site $typo3Site, string $property) { - if ($scope === 'write' && !self::writeConnectionIsEnabled($typo3Site, $languageId)) { - $scope = 'read'; - } - // convention key meilisearch_$property_$scope - $keyToCheck = 'meilisearch_' . $property . '_' . $scope; - - // convention fallback key meilisearch_$property_read - $fallbackKey = 'meilisearch_' . $property . '_read'; - - // try to find language specific setting if found return it - $languageSpecificConfiguration = $typo3Site->getLanguageById($languageId)->toArray(); - $value = self::getValueOrFallback($languageSpecificConfiguration, $keyToCheck, $fallbackKey); - if ($value !== null) { - return $value; - } + $keyToCheck = 'meilisearch_' . $property; // if not found check global configuration $siteBaseConfiguration = $typo3Site->getConfiguration(); - return self::getValueOrFallback($siteBaseConfiguration, $keyToCheck, $fallbackKey); + return self::getValueOrFallback($siteBaseConfiguration, $keyToCheck); } - /** - * Checks whether write connection is enabled. - * Language context properties have precedence over global settings. - * - * @param Site $typo3Site - * @param int $languageId - * @return bool - */ - protected static function writeConnectionIsEnabled(Site $typo3Site, int $languageId): bool - { - $languageSpecificConfiguration = $typo3Site->getLanguageById($languageId)->toArray(); - $value = self::getValueOrFallback($languageSpecificConfiguration, 'meilisearch_use_write_connection', 'meilisearch_use_write_connection'); - if ($value !== null) { - return $value; - } - - $siteBaseConfiguration = $typo3Site->getConfiguration(); - $value = self::getValueOrFallback($siteBaseConfiguration, 'meilisearch_use_write_connection', 'meilisearch_use_write_connection'); - if ($value !== null) { - return $value; - } - return false; - } /** * @param array $data * @param string $keyToCheck - * @param string $fallbackKey * @return string|bool|null */ - protected static function getValueOrFallback(array $data, string $keyToCheck, string $fallbackKey) + protected static function getValueOrFallback(array $data, string $keyToCheck) { $value = $data[$keyToCheck] ?? null; if ($value === '0' || $value === 0 || !empty($value)) { return self::evaluateConfigurationData($value); } - - return self::evaluateConfigurationData($data[$fallbackKey] ?? null); + return null; } /** @@ -176,7 +121,8 @@ class SiteUtility { if ($value === 'true') { return true; - } elseif ($value === 'false') { + } + if ($value === 'false') { return false; } diff --git a/Classes/Task/ReIndexTask.php b/Classes/Task/ReIndexTask.php index 701a49d..bf169fe 100644 --- a/Classes/Task/ReIndexTask.php +++ b/Classes/Task/ReIndexTask.php @@ -75,7 +75,7 @@ class ReIndexTask extends AbstractMeilisearchTask { $cleanUpResult = true; $meilisearchConfiguration = $this->getSite()->getMeilisearchConfiguration(); - $meilisearchServers = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionsBySite($this->getSite()); + $meilisearchServers = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionBySite($this->getSite()); $typesToCleanUp = []; $enableCommitsSetting = $meilisearchConfiguration->getEnableCommits(); diff --git a/Classes/Typo3PageIndexer.php b/Classes/Typo3PageIndexer.php index c1c6dc6..fc5407f 100644 --- a/Classes/Typo3PageIndexer.php +++ b/Classes/Typo3PageIndexer.php @@ -25,7 +25,7 @@ namespace WapplerSystems\Meilisearch; ***************************************************************/ use WapplerSystems\Meilisearch\Access\Rootline; -use WapplerSystems\Meilisearch\Domain\Search\ApacheMeilisearchDocument\Builder; +use WapplerSystems\Meilisearch\Domain\Search\MeilisearchDocument\Builder; use WapplerSystems\Meilisearch\FieldProcessor\Service; use WapplerSystems\Meilisearch\IndexQueue\FrontendHelper\PageFieldMappingIndexer; use WapplerSystems\Meilisearch\IndexQueue\Item; diff --git a/Classes/ViewHelpers/AbstractSolrFrontendTagBasedViewHelper.php b/Classes/ViewHelpers/AbstractMeilisearchFrontendTagBasedViewHelper.php similarity index 100% rename from Classes/ViewHelpers/AbstractSolrFrontendTagBasedViewHelper.php rename to Classes/ViewHelpers/AbstractMeilisearchFrontendTagBasedViewHelper.php diff --git a/Classes/ViewHelpers/AbstractSolrFrontendViewHelper.php b/Classes/ViewHelpers/AbstractMeilisearchFrontendViewHelper.php similarity index 100% rename from Classes/ViewHelpers/AbstractSolrFrontendViewHelper.php rename to Classes/ViewHelpers/AbstractMeilisearchFrontendViewHelper.php diff --git a/Classes/ViewHelpers/AbstractSolrTagBasedViewHelper.php b/Classes/ViewHelpers/AbstractMeilisearchTagBasedViewHelper.php similarity index 100% rename from Classes/ViewHelpers/AbstractSolrTagBasedViewHelper.php rename to Classes/ViewHelpers/AbstractMeilisearchTagBasedViewHelper.php diff --git a/Classes/ViewHelpers/AbstractSolrViewHelper.php b/Classes/ViewHelpers/AbstractMeilisearchViewHelper.php similarity index 100% rename from Classes/ViewHelpers/AbstractSolrViewHelper.php rename to Classes/ViewHelpers/AbstractMeilisearchViewHelper.php diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php index c2f496a..bfc37f2 100644 --- a/Configuration/TCA/Overrides/sys_template.php +++ b/Configuration/TCA/Overrides/sys_template.php @@ -2,13 +2,11 @@ // TypoScript \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile('meilisearch', - 'Configuration/TypoScript/Meilisearch/', 'Search - Base Configuration'); + 'Configuration/TypoScript/Meilisearch/', 'Meilisearch'); // StyleSheets \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile('meilisearch', - 'Configuration/TypoScript/BootstrapCss/', 'Search - Bootstrap CSS Framework'); -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile('meilisearch', - 'Configuration/TypoScript/StyleSheets/', 'Search - Default Stylesheets'); + 'Configuration/TypoScript/BootstrapCSS/', 'Meilisearch - Bootstrap CSS'); // OpenSearch++ \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile('meilisearch', diff --git a/Configuration/TypoScript/BootstrapCss/setup.typoscript b/Configuration/TypoScript/BootstrapCSS/setup.typoscript similarity index 100% rename from Configuration/TypoScript/BootstrapCss/setup.typoscript rename to Configuration/TypoScript/BootstrapCSS/setup.typoscript diff --git a/Configuration/TypoScript/BootstrapCss/setup.txt b/Configuration/TypoScript/BootstrapCss/setup.txt deleted file mode 100644 index 4b043e0..0000000 --- a/Configuration/TypoScript/BootstrapCss/setup.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Important: This file is deprecated and will removed with EXT:meilisearch 12.x -@import 'EXT:meilisearch/Configuration/TypoScript/BootstrapCss/setup.typoscript' diff --git a/Configuration/TypoScript/Meilisearch/constants.typoscript b/Configuration/TypoScript/Meilisearch/constants.typoscript new file mode 100644 index 0000000..f93d813 --- /dev/null +++ b/Configuration/TypoScript/Meilisearch/constants.typoscript @@ -0,0 +1,28 @@ +plugin.tx_meilisearch { + # cat=meilisearch: basic/10/enable; type=boolean; label=Enable/disable Meilisearch extension: EXT:meilisearch should only be enabled for relevant sys_languages, to avoid unnecessary connections and overwritten contents. + enabled = 1 + + view { + # cat=plugin.tx_meilisearch/file; type=string; label=Path to template root (FE) + templateRootPath = EXT:meilisearch/Resources/Private/Templates/ + # cat=plugin.tx_meilisearch/file; type=string; label=Path to template partials (FE) + partialRootPath = EXT:meilisearch/Resources/Private/Partials/ + # cat=plugin.tx_meilisearch/file; type=string; label=Path to template layouts (FE) + layoutRootPath = EXT:meilisearch/Resources/Private/Layouts/ + } + + meilisearch { + scheme = http + host = localhost + port = 7700 + apiKey = + } + + search { + targetPage = 0 + + results { + resultsPerPage = 10 + } + } +} diff --git a/Configuration/TypoScript/Solr/setup.typoscript b/Configuration/TypoScript/Meilisearch/setup.typoscript similarity index 85% rename from Configuration/TypoScript/Solr/setup.typoscript rename to Configuration/TypoScript/Meilisearch/setup.typoscript index db15e40..69575a6 100644 --- a/Configuration/TypoScript/Solr/setup.typoscript +++ b/Configuration/TypoScript/Meilisearch/setup.typoscript @@ -8,20 +8,31 @@ plugin.tx_meilisearch { dateFormat.date = d.m.Y H:i } - meilisearch { - read { - scheme = {$plugin.tx_meilisearch.meilisearch.scheme} - host = {$plugin.tx_meilisearch.meilisearch.host} - port = {$plugin.tx_meilisearch.meilisearch.port} - apiKey = {$plugin.tx_meilisearch.meilisearch.apiKey} + view { + pluginNamespace = tx_meilisearch + + templateRootPaths { + 0 = EXT:meilisearch/Resources/Private/Templates/ + 10 = {$plugin.tx_meilisearch.view.templateRootPath} } - write { - scheme = {$plugin.tx_meilisearch.meilisearch.scheme} - host = {$plugin.tx_meilisearch.meilisearch.host} - port = {$plugin.tx_meilisearch.meilisearch.port} - apiKey = {$plugin.tx_meilisearch.meilisearch.apiKey} + partialRootPaths { + 0 = EXT:meilisearch/Resources/Private/Partials/ + 10 = {$plugin.tx_meilisearch.view.partialRootPath} } + + layoutRootPaths { + 0 = EXT:meilisearch/Resources/Private/Layouts/ + 10 = {$plugin.tx_meilisearch.view.layoutRootPath} + } + + } + + meilisearch { + scheme = {$plugin.tx_meilisearch.meilisearch.scheme} + host = {$plugin.tx_meilisearch.meilisearch.host} + port = {$plugin.tx_meilisearch.meilisearch.port} + apiKey = {$plugin.tx_meilisearch.meilisearch.apiKey} } index { @@ -39,8 +50,6 @@ plugin.tx_meilisearch { queue { - // mapping tableName.fields.MeilisearchFieldName => TableFieldName (+ cObj processing) - pages = 1 pages { initialization = WapplerSystems\Meilisearch\IndexQueue\Initializer\Page @@ -116,9 +125,6 @@ plugin.tx_meilisearch { sortBy = - // https://www.hathitrust.org/blogs/large-scale-search/slow-queries-and-common-words-part-2 - // http://blog.thedigitalgroup.com/vijaym/understanding-phrasequery-and-slop-in-meilisearch/ - // https://meilisearch.pl/en/2010/07/14/meilisearch-and-phrasequery-phrase-bonus-in-query-stage/ // see https://lucene.apache.org/meilisearch/guide/7_0/the-dismax-query-parser.html#TheDisMaxQueryParser-Thepf_PhraseFields_Parameter // EXT:Meilisearch configures Schemas from Meilisearch to use content field with boost of 2.0 per default. @@ -313,47 +319,6 @@ plugin.tx_meilisearch { anonymizeIP = 1 } - view { - pluginNamespace = tx_meilisearch - - templateRootPaths { - 0 = EXT:meilisearch/Resources/Private/Templates/ - 10 = {$plugin.tx_meilisearch.view.templateRootPath} - } - - partialRootPaths { - 0 = EXT:meilisearch/Resources/Private/Partials/ - 10 = {$plugin.tx_meilisearch.view.partialRootPath} - } - - layoutRootPaths { - 0 = EXT:meilisearch/Resources/Private/Layouts/ - 10 = {$plugin.tx_meilisearch.view.layoutRootPath} - } - - // By convention the templates is loaded from EXT:meilisearch/Resources/Private/Templates/Frontend/Search/(ActionName).html - // If you want to define a different entry template, you can do this here to overwrite the conventional default template - // if you want to use FLUID fallbacks you can just configure the template name, otherwise you could also use a full reference EXT:/.../ - // The templates that you configure in availableTemplate can be used in the flexform by the editor to select a template for the concrete plugin instance. - templateFiles { - // results = Results - // results.availableTemplates { - // default { - // label = Default Searchresults Template - // file = Results - // } - // } - // form = Form - // form.availableTemplates { - // default { - // label = Default Searchform Template - // file = Form - // } - // } - // frequentSearched = FrequentlySearched - } - } - logging { exceptions = 1 debugOutput = 0 diff --git a/Configuration/TypoScript/OpenSearch/constants.txt b/Configuration/TypoScript/OpenSearch/constants.txt deleted file mode 100644 index 39ec0b8..0000000 --- a/Configuration/TypoScript/OpenSearch/constants.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Important: This file is deprecated and will removed with EXT:meilisearch 12.x -@import 'EXT:meilisearch/Configuration/TypoScript/OpenSearch/constants.typoscript' diff --git a/Configuration/TypoScript/OpenSearch/constants.typoscript b/Configuration/TypoScript/OpenSearch/constants.typoscript index 3485b62..9aa87ba 100644 --- a/Configuration/TypoScript/OpenSearch/constants.typoscript +++ b/Configuration/TypoScript/OpenSearch/constants.typoscript @@ -1,10 +1,9 @@ - plugin.tx_meilisearch.OpenSearch { - # cat=plugin.meilisearch: OpenSearch/10; type=text; label=Short Name: Brief title of the search, up to 16 characters. - shortName = Website Search + # cat=plugin.meilisearch: OpenSearch/10; type=text; label=Short Name: Brief title of the search, up to 16 characters. + shortName = Website Search - # cat=plugin.meilisearch: OpenSearch/20; type=text; label=Description: Textual description of the search. - description = Search this website + # cat=plugin.meilisearch: OpenSearch/20; type=text; label=Description: Textual description of the search. + description = Search this website } diff --git a/Configuration/TypoScript/OpenSearch/setup.txt b/Configuration/TypoScript/OpenSearch/setup.txt deleted file mode 100644 index 5865d52..0000000 --- a/Configuration/TypoScript/OpenSearch/setup.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Important: This file is deprecated and will removed with EXT:meilisearch 12.x -@import 'EXT:meilisearch/Configuration/TypoScript/OpenSearch/setup.typoscript' diff --git a/Configuration/TypoScript/OpenSearch/setup.typoscript b/Configuration/TypoScript/OpenSearch/setup.typoscript index 3ed613e..301807e 100644 --- a/Configuration/TypoScript/OpenSearch/setup.typoscript +++ b/Configuration/TypoScript/OpenSearch/setup.typoscript @@ -1,42 +1,41 @@ - // 7567 = SOLR as vanity number page { - headerData.7567 = TEXT - headerData.7567 { - typolink.parameter.data = leveluid:0 - typolink.parameter.wrap = |,7567 - typolink.forceAbsoluteUrl = 1 - typolink.returnLast = url + headerData.7567 = TEXT + headerData.7567 { + typolink.parameter.data = leveluid:0 + typolink.parameter.wrap = |,7567 + typolink.forceAbsoluteUrl = 1 + typolink.returnLast = url - wrap ( + wrap ( - ) - } + ) + } } OpenSearch = PAGE OpenSearch { - typeNum = 7567 + typeNum = 7567 - config { - disableAllHeaderCode = 1 - additionalHeaders.10.header = Content-type:application/opensearchdescription+xml - xhtml_cleaning = 0 - admPanel = 0 - } + config { + disableAllHeaderCode = 1 + additionalHeaders.10.header = Content-type:application/opensearchdescription+xml + xhtml_cleaning = 0 + admPanel = 0 + } - 10 = TEXT - 10.insertData = 1 - 10.value ( + 10 = TEXT + 10.insertData = 1 + 10.value ( {$plugin.tx_meilisearch.OpenSearch.shortName} @@ -44,61 +43,57 @@ OpenSearch { UTF-8 UTF-8 - ) + ) - 11 = TEXT - 11.insertData = 1 - 11.value ( + 11 = TEXT + 11.insertData = 1 + 11.value ( {getIndpEnv:TYPO3_SITE_URL}favicon.ico - ) + ) - - # link to this description file for automatic updates - 20 = TEXT - 20.insertData = 1 - 20.value ( + # link to this description file for automatic updates + 20 = TEXT + 20.insertData = 1 + 20.value ( - ) + ) - - # link the search page - 30 = TEXT - 30.insertData = 1 - 30.value ( + # link the search page + 30 = TEXT + 30.insertData = 1 + 30.value ( + 31 = TEXT + 31.value = q + 31.wrap = &|={searchTerms}" /> - - # link to auto suggestion provider - 40 = TEXT - 40.insertData = 1 - 40.value ( + # link to auto suggestion provider + 40 = TEXT + 40.insertData = 1 + 40.value ( + 41 = TEXT + 41.value = q + 41.wrap = &|={searchTerms}" /> - - 500 = TEXT - 500.value ( + 500 = TEXT + 500.value ( - ) + ) } diff --git a/Configuration/TypoScript/Solr/constants.txt b/Configuration/TypoScript/Solr/constants.txt deleted file mode 100644 index 8ab3889..0000000 --- a/Configuration/TypoScript/Solr/constants.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Important: This file is deprecated and will removed with EXT:meilisearch 12.x -@import 'EXT:meilisearch/Configuration/TypoScript/Meilisearch/constants.typoscript' diff --git a/Configuration/TypoScript/Solr/constants.typoscript b/Configuration/TypoScript/Solr/constants.typoscript deleted file mode 100644 index 7fb71c2..0000000 --- a/Configuration/TypoScript/Solr/constants.typoscript +++ /dev/null @@ -1,27 +0,0 @@ -plugin.tx_meilisearch { - # cat=meilisearch: basic/10/enable; type=boolean; label=Enable/disable Meilisearch extension: EXT:meilisearch should only be enabled for relevant sys_languages, to avoid unnecessary connections and overwritten contents. - enabled = 1 - - view { - templateRootPath = - partialRootPath = - layoutRootPath = - } - - meilisearch { - scheme = http - host = localhost - port = 7700 - path = /meilisearch/core_en/ - username = - password = - } - - search { - targetPage = 0 - - results { - resultsPerPage = 10 - } - } -} diff --git a/Configuration/TypoScript/Solr/setup.txt b/Configuration/TypoScript/Solr/setup.txt deleted file mode 100644 index 62a76b1..0000000 --- a/Configuration/TypoScript/Solr/setup.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Important: This file is deprecated and will removed with EXT:meilisearch 12.x -@import 'EXT:meilisearch/Configuration/TypoScript/Meilisearch/setup.typoscript' diff --git a/Configuration/TypoScript/StyleSheets/setup.txt b/Configuration/TypoScript/StyleSheets/setup.txt deleted file mode 100644 index a8844b3..0000000 --- a/Configuration/TypoScript/StyleSheets/setup.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Important: This file is deprecated and will removed with EXT:meilisearch 12.x -@import 'EXT:meilisearch/Configuration/TypoScript/StyleSheets/setup.typoscript' diff --git a/Configuration/TypoScript/StyleSheets/setup.typoscript b/Configuration/TypoScript/StyleSheets/setup.typoscript index ade069b..808cb09 100644 --- a/Configuration/TypoScript/StyleSheets/setup.typoscript +++ b/Configuration/TypoScript/StyleSheets/setup.typoscript @@ -1,4 +1,4 @@ page.includeCSS { - search = EXT:meilisearch/Resources/Public/StyleSheets/Frontend/results.css - meilisearch-loader = EXT:meilisearch/Resources/Public/StyleSheets/Frontend/loader.css + search = EXT:meilisearch/Resources/Public/StyleSheets/Frontend/results.css + meilisearch-loader = EXT:meilisearch/Resources/Public/StyleSheets/Frontend/loader.css } diff --git a/Resources/Private/Templates/Backend/Search/InfoModule/Index.html b/Resources/Private/Templates/Backend/Search/InfoModule/Index.html index 09f0c17..975fcef 100644 --- a/Resources/Private/Templates/Backend/Search/InfoModule/Index.html +++ b/Resources/Private/Templates/Backend/Search/InfoModule/Index.html @@ -102,7 +102,6 @@
- No meilisearch server running? You can use hosted-meilisearch.com to setup a meilisearch core with just a few clicks. diff --git a/Resources/Public/JavaScript/Bootstrap/bootstrap.js b/Resources/Public/JavaScript/Bootstrap/bootstrap.js deleted file mode 100644 index 8a2e99a..0000000 --- a/Resources/Public/JavaScript/Bootstrap/bootstrap.js +++ /dev/null @@ -1,2377 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under the MIT license - */ - -if (typeof jQuery === 'undefined') { - throw new Error('Bootstrap\'s JavaScript requires jQuery') -} - -+function ($) { - 'use strict'; - var version = $.fn.jquery.split(' ')[0].split('.') - if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) { - throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4') - } -}(jQuery); - -/* ======================================================================== - * Bootstrap: transition.js v3.3.7 - * http://getbootstrap.com/javascript/#transitions - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) - // ============================================================ - - function transitionEnd() { - var el = document.createElement('bootstrap') - - var transEndEventNames = { - WebkitTransition : 'webkitTransitionEnd', - MozTransition : 'transitionend', - OTransition : 'oTransitionEnd otransitionend', - transition : 'transitionend' - } - - for (var name in transEndEventNames) { - if (el.style[name] !== undefined) { - return { end: transEndEventNames[name] } - } - } - - return false // explicit for ie8 ( ._.) - } - - // http://blog.alexmaccaw.com/css-transitions - $.fn.emulateTransitionEnd = function (duration) { - var called = false - var $el = this - $(this).one('bsTransitionEnd', function () { called = true }) - var callback = function () { if (!called) $($el).trigger($.support.transition.end) } - setTimeout(callback, duration) - return this - } - - $(function () { - $.support.transition = transitionEnd() - - if (!$.support.transition) return - - $.event.special.bsTransitionEnd = { - bindType: $.support.transition.end, - delegateType: $.support.transition.end, - handle: function (e) { - if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) - } - } - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: alert.js v3.3.7 - * http://getbootstrap.com/javascript/#alerts - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // ALERT CLASS DEFINITION - // ====================== - - var dismiss = '[data-dismiss="alert"]' - var Alert = function (el) { - $(el).on('click', dismiss, this.close) - } - - Alert.VERSION = '3.3.7' - - Alert.TRANSITION_DURATION = 150 - - Alert.prototype.close = function (e) { - var $this = $(this) - var selector = $this.attr('data-target') - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 - } - - var $parent = $(selector === '#' ? [] : selector) - - if (e) e.preventDefault() - - if (!$parent.length) { - $parent = $this.closest('.alert') - } - - $parent.trigger(e = $.Event('close.bs.alert')) - - if (e.isDefaultPrevented()) return - - $parent.removeClass('in') - - function removeElement() { - // detach from parent, fire event then clean up data - $parent.detach().trigger('closed.bs.alert').remove() - } - - $.support.transition && $parent.hasClass('fade') ? - $parent - .one('bsTransitionEnd', removeElement) - .emulateTransitionEnd(Alert.TRANSITION_DURATION) : - removeElement() - } - - - // ALERT PLUGIN DEFINITION - // ======================= - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.alert') - - if (!data) $this.data('bs.alert', (data = new Alert(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - var old = $.fn.alert - - $.fn.alert = Plugin - $.fn.alert.Constructor = Alert - - - // ALERT NO CONFLICT - // ================= - - $.fn.alert.noConflict = function () { - $.fn.alert = old - return this - } - - - // ALERT DATA-API - // ============== - - $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: button.js v3.3.7 - * http://getbootstrap.com/javascript/#buttons - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // BUTTON PUBLIC CLASS DEFINITION - // ============================== - - var Button = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, Button.DEFAULTS, options) - this.isLoading = false - } - - Button.VERSION = '3.3.7' - - Button.DEFAULTS = { - loadingText: 'loading...' - } - - Button.prototype.setState = function (state) { - var d = 'disabled' - var $el = this.$element - var val = $el.is('input') ? 'val' : 'html' - var data = $el.data() - - state += 'Text' - - if (data.resetText == null) $el.data('resetText', $el[val]()) - - // push to event loop to allow forms to submit - setTimeout($.proxy(function () { - $el[val](data[state] == null ? this.options[state] : data[state]) - - if (state == 'loadingText') { - this.isLoading = true - $el.addClass(d).attr(d, d).prop(d, true) - } else if (this.isLoading) { - this.isLoading = false - $el.removeClass(d).removeAttr(d).prop(d, false) - } - }, this), 0) - } - - Button.prototype.toggle = function () { - var changed = true - var $parent = this.$element.closest('[data-toggle="buttons"]') - - if ($parent.length) { - var $input = this.$element.find('input') - if ($input.prop('type') == 'radio') { - if ($input.prop('checked')) changed = false - $parent.find('.active').removeClass('active') - this.$element.addClass('active') - } else if ($input.prop('type') == 'checkbox') { - if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false - this.$element.toggleClass('active') - } - $input.prop('checked', this.$element.hasClass('active')) - if (changed) $input.trigger('change') - } else { - this.$element.attr('aria-pressed', !this.$element.hasClass('active')) - this.$element.toggleClass('active') - } - } - - - // BUTTON PLUGIN DEFINITION - // ======================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.button') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.button', (data = new Button(this, options))) - - if (option == 'toggle') data.toggle() - else if (option) data.setState(option) - }) - } - - var old = $.fn.button - - $.fn.button = Plugin - $.fn.button.Constructor = Button - - - // BUTTON NO CONFLICT - // ================== - - $.fn.button.noConflict = function () { - $.fn.button = old - return this - } - - - // BUTTON DATA-API - // =============== - - $(document) - .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { - var $btn = $(e.target).closest('.btn') - Plugin.call($btn, 'toggle') - if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { - // Prevent double click on radios, and the double selections (so cancellation) on checkboxes - e.preventDefault() - // The target component still receive the focus - if ($btn.is('input,button')) $btn.trigger('focus') - else $btn.find('input:visible,button:visible').first().trigger('focus') - } - }) - .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { - $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: carousel.js v3.3.7 - * http://getbootstrap.com/javascript/#carousel - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // CAROUSEL CLASS DEFINITION - // ========================= - - var Carousel = function (element, options) { - this.$element = $(element) - this.$indicators = this.$element.find('.carousel-indicators') - this.options = options - this.paused = null - this.sliding = null - this.interval = null - this.$active = null - this.$items = null - - this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) - - this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element - .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) - .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) - } - - Carousel.VERSION = '3.3.7' - - Carousel.TRANSITION_DURATION = 600 - - Carousel.DEFAULTS = { - interval: 5000, - pause: 'hover', - wrap: true, - keyboard: true - } - - Carousel.prototype.keydown = function (e) { - if (/input|textarea/i.test(e.target.tagName)) return - switch (e.which) { - case 37: this.prev(); break - case 39: this.next(); break - default: return - } - - e.preventDefault() - } - - Carousel.prototype.cycle = function (e) { - e || (this.paused = false) - - this.interval && clearInterval(this.interval) - - this.options.interval - && !this.paused - && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) - - return this - } - - Carousel.prototype.getItemIndex = function (item) { - this.$items = item.parent().children('.item') - return this.$items.index(item || this.$active) - } - - Carousel.prototype.getItemForDirection = function (direction, active) { - var activeIndex = this.getItemIndex(active) - var willWrap = (direction == 'prev' && activeIndex === 0) - || (direction == 'next' && activeIndex == (this.$items.length - 1)) - if (willWrap && !this.options.wrap) return active - var delta = direction == 'prev' ? -1 : 1 - var itemIndex = (activeIndex + delta) % this.$items.length - return this.$items.eq(itemIndex) - } - - Carousel.prototype.to = function (pos) { - var that = this - var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) - - if (pos > (this.$items.length - 1) || pos < 0) return - - if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" - if (activeIndex == pos) return this.pause().cycle() - - return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) - } - - Carousel.prototype.pause = function (e) { - e || (this.paused = true) - - if (this.$element.find('.next, .prev').length && $.support.transition) { - this.$element.trigger($.support.transition.end) - this.cycle(true) - } - - this.interval = clearInterval(this.interval) - - return this - } - - Carousel.prototype.next = function () { - if (this.sliding) return - return this.slide('next') - } - - Carousel.prototype.prev = function () { - if (this.sliding) return - return this.slide('prev') - } - - Carousel.prototype.slide = function (type, next) { - var $active = this.$element.find('.item.active') - var $next = next || this.getItemForDirection(type, $active) - var isCycling = this.interval - var direction = type == 'next' ? 'left' : 'right' - var that = this - - if ($next.hasClass('active')) return (this.sliding = false) - - var relatedTarget = $next[0] - var slideEvent = $.Event('slide.bs.carousel', { - relatedTarget: relatedTarget, - direction: direction - }) - this.$element.trigger(slideEvent) - if (slideEvent.isDefaultPrevented()) return - - this.sliding = true - - isCycling && this.pause() - - if (this.$indicators.length) { - this.$indicators.find('.active').removeClass('active') - var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) - $nextIndicator && $nextIndicator.addClass('active') - } - - var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" - if ($.support.transition && this.$element.hasClass('slide')) { - $next.addClass(type) - $next[0].offsetWidth // force reflow - $active.addClass(direction) - $next.addClass(direction) - $active - .one('bsTransitionEnd', function () { - $next.removeClass([type, direction].join(' ')).addClass('active') - $active.removeClass(['active', direction].join(' ')) - that.sliding = false - setTimeout(function () { - that.$element.trigger(slidEvent) - }, 0) - }) - .emulateTransitionEnd(Carousel.TRANSITION_DURATION) - } else { - $active.removeClass('active') - $next.addClass('active') - this.sliding = false - this.$element.trigger(slidEvent) - } - - isCycling && this.cycle() - - return this - } - - - // CAROUSEL PLUGIN DEFINITION - // ========================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.carousel') - var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) - var action = typeof option == 'string' ? option : options.slide - - if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) - if (typeof option == 'number') data.to(option) - else if (action) data[action]() - else if (options.interval) data.pause().cycle() - }) - } - - var old = $.fn.carousel - - $.fn.carousel = Plugin - $.fn.carousel.Constructor = Carousel - - - // CAROUSEL NO CONFLICT - // ==================== - - $.fn.carousel.noConflict = function () { - $.fn.carousel = old - return this - } - - - // CAROUSEL DATA-API - // ================= - - var clickHandler = function (e) { - var href - var $this = $(this) - var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 - if (!$target.hasClass('carousel')) return - var options = $.extend({}, $target.data(), $this.data()) - var slideIndex = $this.attr('data-slide-to') - if (slideIndex) options.interval = false - - Plugin.call($target, options) - - if (slideIndex) { - $target.data('bs.carousel').to(slideIndex) - } - - e.preventDefault() - } - - $(document) - .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) - .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) - - $(window).on('load', function () { - $('[data-ride="carousel"]').each(function () { - var $carousel = $(this) - Plugin.call($carousel, $carousel.data()) - }) - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: collapse.js v3.3.7 - * http://getbootstrap.com/javascript/#collapse - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - -/* jshint latedef: false */ - -+function ($) { - 'use strict'; - - // COLLAPSE PUBLIC CLASS DEFINITION - // ================================ - - var Collapse = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, Collapse.DEFAULTS, options) - this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + - '[data-toggle="collapse"][data-target="#' + element.id + '"]') - this.transitioning = null - - if (this.options.parent) { - this.$parent = this.getParent() - } else { - this.addAriaAndCollapsedClass(this.$element, this.$trigger) - } - - if (this.options.toggle) this.toggle() - } - - Collapse.VERSION = '3.3.7' - - Collapse.TRANSITION_DURATION = 350 - - Collapse.DEFAULTS = { - toggle: true - } - - Collapse.prototype.dimension = function () { - var hasWidth = this.$element.hasClass('width') - return hasWidth ? 'width' : 'height' - } - - Collapse.prototype.show = function () { - if (this.transitioning || this.$element.hasClass('in')) return - - var activesData - var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') - - if (actives && actives.length) { - activesData = actives.data('bs.collapse') - if (activesData && activesData.transitioning) return - } - - var startEvent = $.Event('show.bs.collapse') - this.$element.trigger(startEvent) - if (startEvent.isDefaultPrevented()) return - - if (actives && actives.length) { - Plugin.call(actives, 'hide') - activesData || actives.data('bs.collapse', null) - } - - var dimension = this.dimension() - - this.$element - .removeClass('collapse') - .addClass('collapsing')[dimension](0) - .attr('aria-expanded', true) - - this.$trigger - .removeClass('collapsed') - .attr('aria-expanded', true) - - this.transitioning = 1 - - var complete = function () { - this.$element - .removeClass('collapsing') - .addClass('collapse in')[dimension]('') - this.transitioning = 0 - this.$element - .trigger('shown.bs.collapse') - } - - if (!$.support.transition) return complete.call(this) - - var scrollSize = $.camelCase(['scroll', dimension].join('-')) - - this.$element - .one('bsTransitionEnd', $.proxy(complete, this)) - .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) - } - - Collapse.prototype.hide = function () { - if (this.transitioning || !this.$element.hasClass('in')) return - - var startEvent = $.Event('hide.bs.collapse') - this.$element.trigger(startEvent) - if (startEvent.isDefaultPrevented()) return - - var dimension = this.dimension() - - this.$element[dimension](this.$element[dimension]())[0].offsetHeight - - this.$element - .addClass('collapsing') - .removeClass('collapse in') - .attr('aria-expanded', false) - - this.$trigger - .addClass('collapsed') - .attr('aria-expanded', false) - - this.transitioning = 1 - - var complete = function () { - this.transitioning = 0 - this.$element - .removeClass('collapsing') - .addClass('collapse') - .trigger('hidden.bs.collapse') - } - - if (!$.support.transition) return complete.call(this) - - this.$element - [dimension](0) - .one('bsTransitionEnd', $.proxy(complete, this)) - .emulateTransitionEnd(Collapse.TRANSITION_DURATION) - } - - Collapse.prototype.toggle = function () { - this[this.$element.hasClass('in') ? 'hide' : 'show']() - } - - Collapse.prototype.getParent = function () { - return $(this.options.parent) - .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') - .each($.proxy(function (i, element) { - var $element = $(element) - this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) - }, this)) - .end() - } - - Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { - var isOpen = $element.hasClass('in') - - $element.attr('aria-expanded', isOpen) - $trigger - .toggleClass('collapsed', !isOpen) - .attr('aria-expanded', isOpen) - } - - function getTargetFromTrigger($trigger) { - var href - var target = $trigger.attr('data-target') - || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 - - return $(target) - } - - - // COLLAPSE PLUGIN DEFINITION - // ========================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.collapse') - var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) - - if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false - if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.collapse - - $.fn.collapse = Plugin - $.fn.collapse.Constructor = Collapse - - - // COLLAPSE NO CONFLICT - // ==================== - - $.fn.collapse.noConflict = function () { - $.fn.collapse = old - return this - } - - - // COLLAPSE DATA-API - // ================= - - $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { - var $this = $(this) - - if (!$this.attr('data-target')) e.preventDefault() - - var $target = getTargetFromTrigger($this) - var data = $target.data('bs.collapse') - var option = data ? 'toggle' : $this.data() - - Plugin.call($target, option) - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: dropdown.js v3.3.7 - * http://getbootstrap.com/javascript/#dropdowns - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // DROPDOWN CLASS DEFINITION - // ========================= - - var backdrop = '.dropdown-backdrop' - var toggle = '[data-toggle="dropdown"]' - var Dropdown = function (element) { - $(element).on('click.bs.dropdown', this.toggle) - } - - Dropdown.VERSION = '3.3.7' - - function getParent($this) { - var selector = $this.attr('data-target') - - if (!selector) { - selector = $this.attr('href') - selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 - } - - var $parent = selector && $(selector) - - return $parent && $parent.length ? $parent : $this.parent() - } - - function clearMenus(e) { - if (e && e.which === 3) return - $(backdrop).remove() - $(toggle).each(function () { - var $this = $(this) - var $parent = getParent($this) - var relatedTarget = { relatedTarget: this } - - if (!$parent.hasClass('open')) return - - if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return - - $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) - - if (e.isDefaultPrevented()) return - - $this.attr('aria-expanded', 'false') - $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) - }) - } - - Dropdown.prototype.toggle = function (e) { - var $this = $(this) - - if ($this.is('.disabled, :disabled')) return - - var $parent = getParent($this) - var isActive = $parent.hasClass('open') - - clearMenus() - - if (!isActive) { - if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { - // if mobile we use a backdrop because click events don't delegate - $(document.createElement('div')) - .addClass('dropdown-backdrop') - .insertAfter($(this)) - .on('click', clearMenus) - } - - var relatedTarget = { relatedTarget: this } - $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) - - if (e.isDefaultPrevented()) return - - $this - .trigger('focus') - .attr('aria-expanded', 'true') - - $parent - .toggleClass('open') - .trigger($.Event('shown.bs.dropdown', relatedTarget)) - } - - return false - } - - Dropdown.prototype.keydown = function (e) { - if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return - - var $this = $(this) - - e.preventDefault() - e.stopPropagation() - - if ($this.is('.disabled, :disabled')) return - - var $parent = getParent($this) - var isActive = $parent.hasClass('open') - - if (!isActive && e.which != 27 || isActive && e.which == 27) { - if (e.which == 27) $parent.find(toggle).trigger('focus') - return $this.trigger('click') - } - - var desc = ' li:not(.disabled):visible a' - var $items = $parent.find('.dropdown-menu' + desc) - - if (!$items.length) return - - var index = $items.index(e.target) - - if (e.which == 38 && index > 0) index-- // up - if (e.which == 40 && index < $items.length - 1) index++ // down - if (!~index) index = 0 - - $items.eq(index).trigger('focus') - } - - - // DROPDOWN PLUGIN DEFINITION - // ========================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.dropdown') - - if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - var old = $.fn.dropdown - - $.fn.dropdown = Plugin - $.fn.dropdown.Constructor = Dropdown - - - // DROPDOWN NO CONFLICT - // ==================== - - $.fn.dropdown.noConflict = function () { - $.fn.dropdown = old - return this - } - - - // APPLY TO STANDARD DROPDOWN ELEMENTS - // =================================== - - $(document) - .on('click.bs.dropdown.data-api', clearMenus) - .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) - .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) - .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) - .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: modal.js v3.3.7 - * http://getbootstrap.com/javascript/#modals - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // MODAL CLASS DEFINITION - // ====================== - - var Modal = function (element, options) { - this.options = options - this.$body = $(document.body) - this.$element = $(element) - this.$dialog = this.$element.find('.modal-dialog') - this.$backdrop = null - this.isShown = null - this.originalBodyPad = null - this.scrollbarWidth = 0 - this.ignoreBackdropClick = false - - if (this.options.remote) { - this.$element - .find('.modal-content') - .load(this.options.remote, $.proxy(function () { - this.$element.trigger('loaded.bs.modal') - }, this)) - } - } - - Modal.VERSION = '3.3.7' - - Modal.TRANSITION_DURATION = 300 - Modal.BACKDROP_TRANSITION_DURATION = 150 - - Modal.DEFAULTS = { - backdrop: true, - keyboard: true, - show: true - } - - Modal.prototype.toggle = function (_relatedTarget) { - return this.isShown ? this.hide() : this.show(_relatedTarget) - } - - Modal.prototype.show = function (_relatedTarget) { - var that = this - var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) - - this.$element.trigger(e) - - if (this.isShown || e.isDefaultPrevented()) return - - this.isShown = true - - this.checkScrollbar() - this.setScrollbar() - this.$body.addClass('modal-open') - - this.escape() - this.resize() - - this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) - - this.$dialog.on('mousedown.dismiss.bs.modal', function () { - that.$element.one('mouseup.dismiss.bs.modal', function (e) { - if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true - }) - }) - - this.backdrop(function () { - var transition = $.support.transition && that.$element.hasClass('fade') - - if (!that.$element.parent().length) { - that.$element.appendTo(that.$body) // don't move modals dom position - } - - that.$element - .show() - .scrollTop(0) - - that.adjustDialog() - - if (transition) { - that.$element[0].offsetWidth // force reflow - } - - that.$element.addClass('in') - - that.enforceFocus() - - var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) - - transition ? - that.$dialog // wait for modal to slide in - .one('bsTransitionEnd', function () { - that.$element.trigger('focus').trigger(e) - }) - .emulateTransitionEnd(Modal.TRANSITION_DURATION) : - that.$element.trigger('focus').trigger(e) - }) - } - - Modal.prototype.hide = function (e) { - if (e) e.preventDefault() - - e = $.Event('hide.bs.modal') - - this.$element.trigger(e) - - if (!this.isShown || e.isDefaultPrevented()) return - - this.isShown = false - - this.escape() - this.resize() - - $(document).off('focusin.bs.modal') - - this.$element - .removeClass('in') - .off('click.dismiss.bs.modal') - .off('mouseup.dismiss.bs.modal') - - this.$dialog.off('mousedown.dismiss.bs.modal') - - $.support.transition && this.$element.hasClass('fade') ? - this.$element - .one('bsTransitionEnd', $.proxy(this.hideModal, this)) - .emulateTransitionEnd(Modal.TRANSITION_DURATION) : - this.hideModal() - } - - Modal.prototype.enforceFocus = function () { - $(document) - .off('focusin.bs.modal') // guard against infinite focus loop - .on('focusin.bs.modal', $.proxy(function (e) { - if (document !== e.target && - this.$element[0] !== e.target && - !this.$element.has(e.target).length) { - this.$element.trigger('focus') - } - }, this)) - } - - Modal.prototype.escape = function () { - if (this.isShown && this.options.keyboard) { - this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { - e.which == 27 && this.hide() - }, this)) - } else if (!this.isShown) { - this.$element.off('keydown.dismiss.bs.modal') - } - } - - Modal.prototype.resize = function () { - if (this.isShown) { - $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) - } else { - $(window).off('resize.bs.modal') - } - } - - Modal.prototype.hideModal = function () { - var that = this - this.$element.hide() - this.backdrop(function () { - that.$body.removeClass('modal-open') - that.resetAdjustments() - that.resetScrollbar() - that.$element.trigger('hidden.bs.modal') - }) - } - - Modal.prototype.removeBackdrop = function () { - this.$backdrop && this.$backdrop.remove() - this.$backdrop = null - } - - Modal.prototype.backdrop = function (callback) { - var that = this - var animate = this.$element.hasClass('fade') ? 'fade' : '' - - if (this.isShown && this.options.backdrop) { - var doAnimate = $.support.transition && animate - - this.$backdrop = $(document.createElement('div')) - .addClass('modal-backdrop ' + animate) - .appendTo(this.$body) - - this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { - if (this.ignoreBackdropClick) { - this.ignoreBackdropClick = false - return - } - if (e.target !== e.currentTarget) return - this.options.backdrop == 'static' - ? this.$element[0].focus() - : this.hide() - }, this)) - - if (doAnimate) this.$backdrop[0].offsetWidth // force reflow - - this.$backdrop.addClass('in') - - if (!callback) return - - doAnimate ? - this.$backdrop - .one('bsTransitionEnd', callback) - .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : - callback() - - } else if (!this.isShown && this.$backdrop) { - this.$backdrop.removeClass('in') - - var callbackRemove = function () { - that.removeBackdrop() - callback && callback() - } - $.support.transition && this.$element.hasClass('fade') ? - this.$backdrop - .one('bsTransitionEnd', callbackRemove) - .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : - callbackRemove() - - } else if (callback) { - callback() - } - } - - // these following methods are used to handle overflowing modals - - Modal.prototype.handleUpdate = function () { - this.adjustDialog() - } - - Modal.prototype.adjustDialog = function () { - var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight - - this.$element.css({ - paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', - paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' - }) - } - - Modal.prototype.resetAdjustments = function () { - this.$element.css({ - paddingLeft: '', - paddingRight: '' - }) - } - - Modal.prototype.checkScrollbar = function () { - var fullWindowWidth = window.innerWidth - if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 - var documentElementRect = document.documentElement.getBoundingClientRect() - fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) - } - this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth - this.scrollbarWidth = this.measureScrollbar() - } - - Modal.prototype.setScrollbar = function () { - var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) - this.originalBodyPad = document.body.style.paddingRight || '' - if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) - } - - Modal.prototype.resetScrollbar = function () { - this.$body.css('padding-right', this.originalBodyPad) - } - - Modal.prototype.measureScrollbar = function () { // thx walsh - var scrollDiv = document.createElement('div') - scrollDiv.className = 'modal-scrollbar-measure' - this.$body.append(scrollDiv) - var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth - this.$body[0].removeChild(scrollDiv) - return scrollbarWidth - } - - - // MODAL PLUGIN DEFINITION - // ======================= - - function Plugin(option, _relatedTarget) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.modal') - var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) - - if (!data) $this.data('bs.modal', (data = new Modal(this, options))) - if (typeof option == 'string') data[option](_relatedTarget) - else if (options.show) data.show(_relatedTarget) - }) - } - - var old = $.fn.modal - - $.fn.modal = Plugin - $.fn.modal.Constructor = Modal - - - // MODAL NO CONFLICT - // ================= - - $.fn.modal.noConflict = function () { - $.fn.modal = old - return this - } - - - // MODAL DATA-API - // ============== - - $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { - var $this = $(this) - var href = $this.attr('href') - var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 - var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) - - if ($this.is('a')) e.preventDefault() - - $target.one('show.bs.modal', function (showEvent) { - if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown - $target.one('hidden.bs.modal', function () { - $this.is(':visible') && $this.trigger('focus') - }) - }) - Plugin.call($target, option, this) - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: tooltip.js v3.3.7 - * http://getbootstrap.com/javascript/#tooltip - * Inspired by the original jQuery.tipsy by Jason Frame - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // TOOLTIP PUBLIC CLASS DEFINITION - // =============================== - - var Tooltip = function (element, options) { - this.type = null - this.options = null - this.enabled = null - this.timeout = null - this.hoverState = null - this.$element = null - this.inState = null - - this.init('tooltip', element, options) - } - - Tooltip.VERSION = '3.3.7' - - Tooltip.TRANSITION_DURATION = 150 - - Tooltip.DEFAULTS = { - animation: true, - placement: 'top', - selector: false, - template: '', - trigger: 'hover focus', - title: '', - delay: 0, - html: false, - container: false, - viewport: { - selector: 'body', - padding: 0 - } - } - - Tooltip.prototype.init = function (type, element, options) { - this.enabled = true - this.type = type - this.$element = $(element) - this.options = this.getOptions(options) - this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) - this.inState = { click: false, hover: false, focus: false } - - if (this.$element[0] instanceof document.constructor && !this.options.selector) { - throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') - } - - var triggers = this.options.trigger.split(' ') - - for (var i = triggers.length; i--;) { - var trigger = triggers[i] - - if (trigger == 'click') { - this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) - } else if (trigger != 'manual') { - var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' - var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' - - this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) - this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) - } - } - - this.options.selector ? - (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : - this.fixTitle() - } - - Tooltip.prototype.getDefaults = function () { - return Tooltip.DEFAULTS - } - - Tooltip.prototype.getOptions = function (options) { - options = $.extend({}, this.getDefaults(), this.$element.data(), options) - - if (options.delay && typeof options.delay == 'number') { - options.delay = { - show: options.delay, - hide: options.delay - } - } - - return options - } - - Tooltip.prototype.getDelegateOptions = function () { - var options = {} - var defaults = this.getDefaults() - - this._options && $.each(this._options, function (key, value) { - if (defaults[key] != value) options[key] = value - }) - - return options - } - - Tooltip.prototype.enter = function (obj) { - var self = obj instanceof this.constructor ? - obj : $(obj.currentTarget).data('bs.' + this.type) - - if (!self) { - self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) - $(obj.currentTarget).data('bs.' + this.type, self) - } - - if (obj instanceof $.Event) { - self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true - } - - if (self.tip().hasClass('in') || self.hoverState == 'in') { - self.hoverState = 'in' - return - } - - clearTimeout(self.timeout) - - self.hoverState = 'in' - - if (!self.options.delay || !self.options.delay.show) return self.show() - - self.timeout = setTimeout(function () { - if (self.hoverState == 'in') self.show() - }, self.options.delay.show) - } - - Tooltip.prototype.isInStateTrue = function () { - for (var key in this.inState) { - if (this.inState[key]) return true - } - - return false - } - - Tooltip.prototype.leave = function (obj) { - var self = obj instanceof this.constructor ? - obj : $(obj.currentTarget).data('bs.' + this.type) - - if (!self) { - self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) - $(obj.currentTarget).data('bs.' + this.type, self) - } - - if (obj instanceof $.Event) { - self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false - } - - if (self.isInStateTrue()) return - - clearTimeout(self.timeout) - - self.hoverState = 'out' - - if (!self.options.delay || !self.options.delay.hide) return self.hide() - - self.timeout = setTimeout(function () { - if (self.hoverState == 'out') self.hide() - }, self.options.delay.hide) - } - - Tooltip.prototype.show = function () { - var e = $.Event('show.bs.' + this.type) - - if (this.hasContent() && this.enabled) { - this.$element.trigger(e) - - var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) - if (e.isDefaultPrevented() || !inDom) return - var that = this - - var $tip = this.tip() - - var tipId = this.getUID(this.type) - - this.setContent() - $tip.attr('id', tipId) - this.$element.attr('aria-describedby', tipId) - - if (this.options.animation) $tip.addClass('fade') - - var placement = typeof this.options.placement == 'function' ? - this.options.placement.call(this, $tip[0], this.$element[0]) : - this.options.placement - - var autoToken = /\s?auto?\s?/i - var autoPlace = autoToken.test(placement) - if (autoPlace) placement = placement.replace(autoToken, '') || 'top' - - $tip - .detach() - .css({ top: 0, left: 0, display: 'block' }) - .addClass(placement) - .data('bs.' + this.type, this) - - this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) - this.$element.trigger('inserted.bs.' + this.type) - - var pos = this.getPosition() - var actualWidth = $tip[0].offsetWidth - var actualHeight = $tip[0].offsetHeight - - if (autoPlace) { - var orgPlacement = placement - var viewportDim = this.getPosition(this.$viewport) - - placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : - placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : - placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : - placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : - placement - - $tip - .removeClass(orgPlacement) - .addClass(placement) - } - - var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) - - this.applyPlacement(calculatedOffset, placement) - - var complete = function () { - var prevHoverState = that.hoverState - that.$element.trigger('shown.bs.' + that.type) - that.hoverState = null - - if (prevHoverState == 'out') that.leave(that) - } - - $.support.transition && this.$tip.hasClass('fade') ? - $tip - .one('bsTransitionEnd', complete) - .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : - complete() - } - } - - Tooltip.prototype.applyPlacement = function (offset, placement) { - var $tip = this.tip() - var width = $tip[0].offsetWidth - var height = $tip[0].offsetHeight - - // manually read margins because getBoundingClientRect includes difference - var marginTop = parseInt($tip.css('margin-top'), 10) - var marginLeft = parseInt($tip.css('margin-left'), 10) - - // we must check for NaN for ie 8/9 - if (isNaN(marginTop)) marginTop = 0 - if (isNaN(marginLeft)) marginLeft = 0 - - offset.top += marginTop - offset.left += marginLeft - - // $.fn.offset doesn't round pixel values - // so we use setOffset directly with our own function B-0 - $.offset.setOffset($tip[0], $.extend({ - using: function (props) { - $tip.css({ - top: Math.round(props.top), - left: Math.round(props.left) - }) - } - }, offset), 0) - - $tip.addClass('in') - - // check to see if placing tip in new offset caused the tip to resize itself - var actualWidth = $tip[0].offsetWidth - var actualHeight = $tip[0].offsetHeight - - if (placement == 'top' && actualHeight != height) { - offset.top = offset.top + height - actualHeight - } - - var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) - - if (delta.left) offset.left += delta.left - else offset.top += delta.top - - var isVertical = /top|bottom/.test(placement) - var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight - var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' - - $tip.offset(offset) - this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) - } - - Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { - this.arrow() - .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') - .css(isVertical ? 'top' : 'left', '') - } - - Tooltip.prototype.setContent = function () { - var $tip = this.tip() - var title = this.getTitle() - - $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) - $tip.removeClass('fade in top bottom left right') - } - - Tooltip.prototype.hide = function (callback) { - var that = this - var $tip = $(this.$tip) - var e = $.Event('hide.bs.' + this.type) - - function complete() { - if (that.hoverState != 'in') $tip.detach() - if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary. - that.$element - .removeAttr('aria-describedby') - .trigger('hidden.bs.' + that.type) - } - callback && callback() - } - - this.$element.trigger(e) - - if (e.isDefaultPrevented()) return - - $tip.removeClass('in') - - $.support.transition && $tip.hasClass('fade') ? - $tip - .one('bsTransitionEnd', complete) - .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : - complete() - - this.hoverState = null - - return this - } - - Tooltip.prototype.fixTitle = function () { - var $e = this.$element - if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { - $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') - } - } - - Tooltip.prototype.hasContent = function () { - return this.getTitle() - } - - Tooltip.prototype.getPosition = function ($element) { - $element = $element || this.$element - - var el = $element[0] - var isBody = el.tagName == 'BODY' - - var elRect = el.getBoundingClientRect() - if (elRect.width == null) { - // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 - elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) - } - var isSvg = window.SVGElement && el instanceof window.SVGElement - // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3. - // See https://github.com/twbs/bootstrap/issues/20280 - var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset()) - var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } - var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null - - return $.extend({}, elRect, scroll, outerDims, elOffset) - } - - Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { - return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : - placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : - placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : - /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } - - } - - Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { - var delta = { top: 0, left: 0 } - if (!this.$viewport) return delta - - var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 - var viewportDimensions = this.getPosition(this.$viewport) - - if (/right|left/.test(placement)) { - var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll - var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight - if (topEdgeOffset < viewportDimensions.top) { // top overflow - delta.top = viewportDimensions.top - topEdgeOffset - } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow - delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset - } - } else { - var leftEdgeOffset = pos.left - viewportPadding - var rightEdgeOffset = pos.left + viewportPadding + actualWidth - if (leftEdgeOffset < viewportDimensions.left) { // left overflow - delta.left = viewportDimensions.left - leftEdgeOffset - } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow - delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset - } - } - - return delta - } - - Tooltip.prototype.getTitle = function () { - var title - var $e = this.$element - var o = this.options - - title = $e.attr('data-original-title') - || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) - - return title - } - - Tooltip.prototype.getUID = function (prefix) { - do prefix += ~~(Math.random() * 1000000) - while (document.getElementById(prefix)) - return prefix - } - - Tooltip.prototype.tip = function () { - if (!this.$tip) { - this.$tip = $(this.options.template) - if (this.$tip.length != 1) { - throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') - } - } - return this.$tip - } - - Tooltip.prototype.arrow = function () { - return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) - } - - Tooltip.prototype.enable = function () { - this.enabled = true - } - - Tooltip.prototype.disable = function () { - this.enabled = false - } - - Tooltip.prototype.toggleEnabled = function () { - this.enabled = !this.enabled - } - - Tooltip.prototype.toggle = function (e) { - var self = this - if (e) { - self = $(e.currentTarget).data('bs.' + this.type) - if (!self) { - self = new this.constructor(e.currentTarget, this.getDelegateOptions()) - $(e.currentTarget).data('bs.' + this.type, self) - } - } - - if (e) { - self.inState.click = !self.inState.click - if (self.isInStateTrue()) self.enter(self) - else self.leave(self) - } else { - self.tip().hasClass('in') ? self.leave(self) : self.enter(self) - } - } - - Tooltip.prototype.destroy = function () { - var that = this - clearTimeout(this.timeout) - this.hide(function () { - that.$element.off('.' + that.type).removeData('bs.' + that.type) - if (that.$tip) { - that.$tip.detach() - } - that.$tip = null - that.$arrow = null - that.$viewport = null - that.$element = null - }) - } - - - // TOOLTIP PLUGIN DEFINITION - // ========================= - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.tooltip') - var options = typeof option == 'object' && option - - if (!data && /destroy|hide/.test(option)) return - if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.tooltip - - $.fn.tooltip = Plugin - $.fn.tooltip.Constructor = Tooltip - - - // TOOLTIP NO CONFLICT - // =================== - - $.fn.tooltip.noConflict = function () { - $.fn.tooltip = old - return this - } - -}(jQuery); - -/* ======================================================================== - * Bootstrap: popover.js v3.3.7 - * http://getbootstrap.com/javascript/#popovers - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // POPOVER PUBLIC CLASS DEFINITION - // =============================== - - var Popover = function (element, options) { - this.init('popover', element, options) - } - - if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') - - Popover.VERSION = '3.3.7' - - Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { - placement: 'right', - trigger: 'click', - content: '', - template: '' - }) - - - // NOTE: POPOVER EXTENDS tooltip.js - // ================================ - - Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) - - Popover.prototype.constructor = Popover - - Popover.prototype.getDefaults = function () { - return Popover.DEFAULTS - } - - Popover.prototype.setContent = function () { - var $tip = this.tip() - var title = this.getTitle() - var content = this.getContent() - - $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) - $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events - this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' - ](content) - - $tip.removeClass('fade top bottom left right in') - - // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do - // this manually by checking the contents. - if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() - } - - Popover.prototype.hasContent = function () { - return this.getTitle() || this.getContent() - } - - Popover.prototype.getContent = function () { - var $e = this.$element - var o = this.options - - return $e.attr('data-content') - || (typeof o.content == 'function' ? - o.content.call($e[0]) : - o.content) - } - - Popover.prototype.arrow = function () { - return (this.$arrow = this.$arrow || this.tip().find('.arrow')) - } - - - // POPOVER PLUGIN DEFINITION - // ========================= - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.popover') - var options = typeof option == 'object' && option - - if (!data && /destroy|hide/.test(option)) return - if (!data) $this.data('bs.popover', (data = new Popover(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.popover - - $.fn.popover = Plugin - $.fn.popover.Constructor = Popover - - - // POPOVER NO CONFLICT - // =================== - - $.fn.popover.noConflict = function () { - $.fn.popover = old - return this - } - -}(jQuery); - -/* ======================================================================== - * Bootstrap: scrollspy.js v3.3.7 - * http://getbootstrap.com/javascript/#scrollspy - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // SCROLLSPY CLASS DEFINITION - // ========================== - - function ScrollSpy(element, options) { - this.$body = $(document.body) - this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) - this.options = $.extend({}, ScrollSpy.DEFAULTS, options) - this.selector = (this.options.target || '') + ' .nav li > a' - this.offsets = [] - this.targets = [] - this.activeTarget = null - this.scrollHeight = 0 - - this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) - this.refresh() - this.process() - } - - ScrollSpy.VERSION = '3.3.7' - - ScrollSpy.DEFAULTS = { - offset: 10 - } - - ScrollSpy.prototype.getScrollHeight = function () { - return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) - } - - ScrollSpy.prototype.refresh = function () { - var that = this - var offsetMethod = 'offset' - var offsetBase = 0 - - this.offsets = [] - this.targets = [] - this.scrollHeight = this.getScrollHeight() - - if (!$.isWindow(this.$scrollElement[0])) { - offsetMethod = 'position' - offsetBase = this.$scrollElement.scrollTop() - } - - this.$body - .find(this.selector) - .map(function () { - var $el = $(this) - var href = $el.data('target') || $el.attr('href') - var $href = /^#./.test(href) && $(href) - - return ($href - && $href.length - && $href.is(':visible') - && [[$href[offsetMethod]().top + offsetBase, href]]) || null - }) - .sort(function (a, b) { return a[0] - b[0] }) - .each(function () { - that.offsets.push(this[0]) - that.targets.push(this[1]) - }) - } - - ScrollSpy.prototype.process = function () { - var scrollTop = this.$scrollElement.scrollTop() + this.options.offset - var scrollHeight = this.getScrollHeight() - var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() - var offsets = this.offsets - var targets = this.targets - var activeTarget = this.activeTarget - var i - - if (this.scrollHeight != scrollHeight) { - this.refresh() - } - - if (scrollTop >= maxScroll) { - return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) - } - - if (activeTarget && scrollTop < offsets[0]) { - this.activeTarget = null - return this.clear() - } - - for (i = offsets.length; i--;) { - activeTarget != targets[i] - && scrollTop >= offsets[i] - && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) - && this.activate(targets[i]) - } - } - - ScrollSpy.prototype.activate = function (target) { - this.activeTarget = target - - this.clear() - - var selector = this.selector + - '[data-target="' + target + '"],' + - this.selector + '[href="' + target + '"]' - - var active = $(selector) - .parents('li') - .addClass('active') - - if (active.parent('.dropdown-menu').length) { - active = active - .closest('li.dropdown') - .addClass('active') - } - - active.trigger('activate.bs.scrollspy') - } - - ScrollSpy.prototype.clear = function () { - $(this.selector) - .parentsUntil(this.options.target, '.active') - .removeClass('active') - } - - - // SCROLLSPY PLUGIN DEFINITION - // =========================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.scrollspy') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.scrollspy - - $.fn.scrollspy = Plugin - $.fn.scrollspy.Constructor = ScrollSpy - - - // SCROLLSPY NO CONFLICT - // ===================== - - $.fn.scrollspy.noConflict = function () { - $.fn.scrollspy = old - return this - } - - - // SCROLLSPY DATA-API - // ================== - - $(window).on('load.bs.scrollspy.data-api', function () { - $('[data-spy="scroll"]').each(function () { - var $spy = $(this) - Plugin.call($spy, $spy.data()) - }) - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: tab.js v3.3.7 - * http://getbootstrap.com/javascript/#tabs - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // TAB CLASS DEFINITION - // ==================== - - var Tab = function (element) { - // jscs:disable requireDollarBeforejQueryAssignment - this.element = $(element) - // jscs:enable requireDollarBeforejQueryAssignment - } - - Tab.VERSION = '3.3.7' - - Tab.TRANSITION_DURATION = 150 - - Tab.prototype.show = function () { - var $this = this.element - var $ul = $this.closest('ul:not(.dropdown-menu)') - var selector = $this.data('target') - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 - } - - if ($this.parent('li').hasClass('active')) return - - var $previous = $ul.find('.active:last a') - var hideEvent = $.Event('hide.bs.tab', { - relatedTarget: $this[0] - }) - var showEvent = $.Event('show.bs.tab', { - relatedTarget: $previous[0] - }) - - $previous.trigger(hideEvent) - $this.trigger(showEvent) - - if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return - - var $target = $(selector) - - this.activate($this.closest('li'), $ul) - this.activate($target, $target.parent(), function () { - $previous.trigger({ - type: 'hidden.bs.tab', - relatedTarget: $this[0] - }) - $this.trigger({ - type: 'shown.bs.tab', - relatedTarget: $previous[0] - }) - }) - } - - Tab.prototype.activate = function (element, container, callback) { - var $active = container.find('> .active') - var transition = callback - && $.support.transition - && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) - - function next() { - $active - .removeClass('active') - .find('> .dropdown-menu > .active') - .removeClass('active') - .end() - .find('[data-toggle="tab"]') - .attr('aria-expanded', false) - - element - .addClass('active') - .find('[data-toggle="tab"]') - .attr('aria-expanded', true) - - if (transition) { - element[0].offsetWidth // reflow for transition - element.addClass('in') - } else { - element.removeClass('fade') - } - - if (element.parent('.dropdown-menu').length) { - element - .closest('li.dropdown') - .addClass('active') - .end() - .find('[data-toggle="tab"]') - .attr('aria-expanded', true) - } - - callback && callback() - } - - $active.length && transition ? - $active - .one('bsTransitionEnd', next) - .emulateTransitionEnd(Tab.TRANSITION_DURATION) : - next() - - $active.removeClass('in') - } - - - // TAB PLUGIN DEFINITION - // ===================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.tab') - - if (!data) $this.data('bs.tab', (data = new Tab(this))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.tab - - $.fn.tab = Plugin - $.fn.tab.Constructor = Tab - - - // TAB NO CONFLICT - // =============== - - $.fn.tab.noConflict = function () { - $.fn.tab = old - return this - } - - - // TAB DATA-API - // ============ - - var clickHandler = function (e) { - e.preventDefault() - Plugin.call($(this), 'show') - } - - $(document) - .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) - .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: affix.js v3.3.7 - * http://getbootstrap.com/javascript/#affix - * ======================================================================== - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // AFFIX CLASS DEFINITION - // ====================== - - var Affix = function (element, options) { - this.options = $.extend({}, Affix.DEFAULTS, options) - - this.$target = $(this.options.target) - .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) - .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) - - this.$element = $(element) - this.affixed = null - this.unpin = null - this.pinnedOffset = null - - this.checkPosition() - } - - Affix.VERSION = '3.3.7' - - Affix.RESET = 'affix affix-top affix-bottom' - - Affix.DEFAULTS = { - offset: 0, - target: window - } - - Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { - var scrollTop = this.$target.scrollTop() - var position = this.$element.offset() - var targetHeight = this.$target.height() - - if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false - - if (this.affixed == 'bottom') { - if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' - return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' - } - - var initializing = this.affixed == null - var colliderTop = initializing ? scrollTop : position.top - var colliderHeight = initializing ? targetHeight : height - - if (offsetTop != null && scrollTop <= offsetTop) return 'top' - if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' - - return false - } - - Affix.prototype.getPinnedOffset = function () { - if (this.pinnedOffset) return this.pinnedOffset - this.$element.removeClass(Affix.RESET).addClass('affix') - var scrollTop = this.$target.scrollTop() - var position = this.$element.offset() - return (this.pinnedOffset = position.top - scrollTop) - } - - Affix.prototype.checkPositionWithEventLoop = function () { - setTimeout($.proxy(this.checkPosition, this), 1) - } - - Affix.prototype.checkPosition = function () { - if (!this.$element.is(':visible')) return - - var height = this.$element.height() - var offset = this.options.offset - var offsetTop = offset.top - var offsetBottom = offset.bottom - var scrollHeight = Math.max($(document).height(), $(document.body).height()) - - if (typeof offset != 'object') offsetBottom = offsetTop = offset - if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) - if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) - - var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) - - if (this.affixed != affix) { - if (this.unpin != null) this.$element.css('top', '') - - var affixType = 'affix' + (affix ? '-' + affix : '') - var e = $.Event(affixType + '.bs.affix') - - this.$element.trigger(e) - - if (e.isDefaultPrevented()) return - - this.affixed = affix - this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null - - this.$element - .removeClass(Affix.RESET) - .addClass(affixType) - .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') - } - - if (affix == 'bottom') { - this.$element.offset({ - top: scrollHeight - height - offsetBottom - }) - } - } - - - // AFFIX PLUGIN DEFINITION - // ======================= - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.affix') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.affix', (data = new Affix(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.affix - - $.fn.affix = Plugin - $.fn.affix.Constructor = Affix - - - // AFFIX NO CONFLICT - // ================= - - $.fn.affix.noConflict = function () { - $.fn.affix = old - return this - } - - - // AFFIX DATA-API - // ============== - - $(window).on('load', function () { - $('[data-spy="affix"]').each(function () { - var $spy = $(this) - var data = $spy.data() - - data.offset = data.offset || {} - - if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom - if (data.offsetTop != null) data.offset.top = data.offsetTop - - Plugin.call($spy, data) - }) - }) - -}(jQuery); diff --git a/Resources/Public/JavaScript/Bootstrap/bootstrap.min.js b/Resources/Public/JavaScript/Bootstrap/bootstrap.min.js deleted file mode 100644 index 9bcd2fc..0000000 --- a/Resources/Public/JavaScript/Bootstrap/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under the MIT license - */ -if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/Resources/Public/JavaScript/Bootstrap/npm.js b/Resources/Public/JavaScript/Bootstrap/npm.js deleted file mode 100644 index bf6aa80..0000000 --- a/Resources/Public/JavaScript/Bootstrap/npm.js +++ /dev/null @@ -1,13 +0,0 @@ -// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. -require('../../js/transition.js') -require('../../js/alert.js') -require('../../js/button.js') -require('../../js/carousel.js') -require('../../js/collapse.js') -require('../../js/dropdown.js') -require('../../js/modal.js') -require('../../js/tooltip.js') -require('../../js/popover.js') -require('../../js/scrollspy.js') -require('../../js/tab.js') -require('../../js/affix.js') \ No newline at end of file diff --git a/ext_tables.php b/ext_tables.php index f8a5781..c3dfd06 100644 --- a/ext_tables.php +++ b/ext_tables.php @@ -63,21 +63,6 @@ defined('TYPO3_MODE') || die(); ] ); - \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule( - 'WapplerSystems.meilisearch', - 'searchbackend', - 'CoreOptimization', - '', - [ - 'Backend\\Search\\CoreOptimizationModule' => 'index, addSynonyms, importSynonymList, deleteAllSynonyms, exportSynonyms, deleteSynonyms, saveStopWords, importStopWordList, exportStopWords, switchSite, switchCore' - ], - [ - 'access' => 'user,group', - 'icon' => 'EXT:meilisearch/Resources/Public/Images/Icons/ModuleCoreOptimization.svg', - 'labels' => 'LLL:EXT:meilisearch/Resources/Private/Language/locallang_mod_coreoptimize.xlf', - 'navigationComponentId' => $treeComponentId - ] - ); \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule( 'WapplerSystems.meilisearch',