* 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 MeiliSearch\Client; use TYPO3\CMS\Core\Utility\DebugUtility; use WapplerSystems\Meilisearch\Domain\Site\Site; use WapplerSystems\Meilisearch\Domain\Site\SiteRepository; use WapplerSystems\Meilisearch\System\Records\Pages\PagesRepository as PagesRepositoryAtExtMeilisearch; use WapplerSystems\Meilisearch\System\Records\SystemLanguage\SystemLanguageRepository; use WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchConnection; use InvalidArgumentException; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; use function json_encode; /** * ConnectionManager is responsible to create MeilisearchConnection objects. * * @author Ingo Renner */ class ConnectionManager implements SingletonInterface { /** * @var array */ protected static $connections = []; /** * @var SystemLanguageRepository */ protected $systemLanguageRepository; /** * @var PagesRepositoryAtExtMeilisearch */ protected $pagesRepositoryAtExtMeilisearch; /** * @var SiteRepository */ protected $siteRepository; /** * @param SystemLanguageRepository $systemLanguageRepository * @param PagesRepositoryAtExtMeilisearch|null $pagesRepositoryAtExtMeilisearch * @param SiteRepository $siteRepository */ public function __construct( SystemLanguageRepository $systemLanguageRepository = null, PagesRepositoryAtExtMeilisearch $pagesRepositoryAtExtMeilisearch = null, SiteRepository $siteRepository = null ) { $this->systemLanguageRepository = $systemLanguageRepository ?? GeneralUtility::makeInstance(SystemLanguageRepository::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 * @return MeilisearchConnection|object */ public function getMeilisearchConnectionForNodes(array $readNodeConfiguration, array $writeNodeConfiguration) { $connectionHash = md5(json_encode($readNodeConfiguration) . json_encode($writeNodeConfiguration)); if (!isset(self::$connections[$connectionHash])) { $readNode = $this->createClientFromArray($readNodeConfiguration); $writeNode = $this->createClientFromArray($writeNodeConfiguration); self::$connections[$connectionHash] = GeneralUtility::makeInstance(MeilisearchConnection::class, $readNode, $writeNode); } return self::$connections[$connectionHash]; } /** * Creates a meilisearch configuration from the configuration array and returns it. * * @param array $config The meilisearch configuration array * @return MeilisearchConnection */ public function getConnectionFromConfiguration(array $config) { if(empty($config['read']) && !empty($config['meilisearchHost'])) { throw new InvalidArgumentException('Invalid registry data please re-initialize your meilisearch connections'); } return $this->getMeilisearchConnectionForNodes($config['read'], $config['write']); } /** * Gets a Meilisearch connection for a page ID. * * @param int $pageId A page ID. * @param int $language The language ID to get the connection for as the path may differ. Optional, defaults to 0. * @param string $mount Comma list of MountPoint parameters * @return MeilisearchConnection A meilisearch connection. * @throws NoMeilisearchConnectionFoundException */ public function getConnectionByPageId($pageId, $language = 0, $mount = '') { try { $site = $this->siteRepository->getSiteByPageId($pageId, $mount); $this->throwExceptionOnInvalidSite($site, 'No site for pageId ' . $pageId); $config = $site->getMeilisearchConnectionConfiguration($language); $meilisearchConnection = $this->getConnectionFromConfiguration($config); return $meilisearchConnection; } catch(InvalidArgumentException $e) { $noMeilisearchConnectionException = $this->buildNoConnectionExceptionForPageAndLanguage($pageId, $language); throw $noMeilisearchConnectionException; } } /** * Gets a Meilisearch connection for a root page ID. * * @param int $pageId A root page ID. * @param int $language The language ID to get the connection for as the path may differ. Optional, defaults to 0. * @return MeilisearchConnection A meilisearch connection. * @throws NoMeilisearchConnectionFoundException */ public function getConnectionByRootPageId($pageId, $language = 0) { try { $site = $this->siteRepository->getSiteByRootPageId($pageId); $this->throwExceptionOnInvalidSite($site, 'No site for pageId ' . $pageId); $config = $site->getMeilisearchConnectionConfiguration($language); $meilisearchConnection = $this->getConnectionFromConfiguration($config); return $meilisearchConnection; } catch (InvalidArgumentException $e) { /* @var NoMeilisearchConnectionFoundException $noMeilisearchConnectionException */ $noMeilisearchConnectionException = $this->buildNoConnectionExceptionForPageAndLanguage($pageId, $language); throw $noMeilisearchConnectionException; } } /** * Gets all connections found. * * @return MeilisearchConnection[] An array of initialized WapplerSystems\Meilisearch\System\Meilisearch\MeilisearchConnection connections * @throws NoMeilisearchConnectionFoundException */ public function getAllConnections() { $meilisearchConnections = []; foreach ($this->siteRepository->getAvailableSites() as $site) { foreach ($site->getAllMeilisearchConnectionConfigurations() as $meilisearchConfiguration) { $meilisearchConnections[] = $this->getConnectionFromConfiguration($meilisearchConfiguration); } } return $meilisearchConnections; } /** * 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 */ public function getConnectionsBySite(Site $site) { $connections = []; foreach ($site->getAllMeilisearchConnectionConfigurations() as $languageId => $meilisearchConnectionConfiguration) { $connections[$languageId] = $this->getConnectionFromConfiguration($meilisearchConnectionConfiguration); } return $connections; } /** * Creates a human readable label from the connections' configuration. * * @param array $connection Connection configuration * @return string Connection label */ protected function buildConnectionLabel(array $connection) { return $connection['rootPageTitle'] . ' (pid: ' . $connection['rootPageUid'] . ', language: ' . $this->systemLanguageRepository->findOneLanguageTitleByLanguageId($connection['language']) . ') - Read node: ' . $connection['read']['host'] . ':' . $connection['read']['port'] . $connection['read']['path'] .' - Write node: ' . $connection['write']['host'] . ':' . $connection['write']['port'] . $connection['write']['path']; } /** * @param $pageId * @param $language * @return NoMeilisearchConnectionFoundException */ protected function buildNoConnectionExceptionForPageAndLanguage($pageId, $language): NoMeilisearchConnectionFoundException { $message = 'Could not find a Meilisearch connection for page [' . $pageId . '] and language [' . $language . '].'; $noMeilisearchConnectionException = $this->buildNoConnectionException($message); $noMeilisearchConnectionException->setLanguageId($language); return $noMeilisearchConnectionException; } /** * Throws a no connection exception when no site was passed. * * @param Site|null $site * @param $message * @throws NoMeilisearchConnectionFoundException */ protected function throwExceptionOnInvalidSite(?Site $site, string $message) { if (!is_null($site)) { return; } throw $this->buildNoConnectionException($message); } /** * Build a NoMeilisearchConnectionFoundException with the passed message. * @param string $message * @return NoMeilisearchConnectionFoundException */ protected function buildNoConnectionException(string $message): NoMeilisearchConnectionFoundException { /* @var NoMeilisearchConnectionFoundException $noMeilisearchConnectionException */ $noMeilisearchConnectionException = GeneralUtility::makeInstance( NoMeilisearchConnectionFoundException::class, /** @scrutinizer ignore-type */ $message, /** @scrutinizer ignore-type */ 1575396474 ); return $noMeilisearchConnectionException; } 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())); } }