Passed
Push — master ( ae3f68...4bca36 )
by Timo
24:00
created

ConnectionManager::setAllConfigurations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
namespace ApacheSolrForTypo3\Solr;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2010-2015 Ingo Renner <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 3 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
28
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
29
use ApacheSolrForTypo3\Solr\System\Page\Rootline;
30
use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository as PagesRepositoryAtExtSolr;
31
use ApacheSolrForTypo3\Solr\System\Records\SystemLanguage\SystemLanguageRepository;
32
use ApacheSolrForTypo3\Solr\System\Solr\Node;
33
use ApacheSolrForTypo3\Solr\System\Solr\SolrConnection;
34
use TYPO3\CMS\Backend\Routing\UriBuilder;
35
use TYPO3\CMS\Backend\Toolbar\ClearCacheActionsHookInterface;
36
use TYPO3\CMS\Core\Registry;
37
use TYPO3\CMS\Core\SingletonInterface;
38
use TYPO3\CMS\Core\TypoScript\ExtendedTemplateService;
39
use TYPO3\CMS\Core\Utility\GeneralUtility;
40
use TYPO3\CMS\Frontend\Page\PageRepository;
41
42
/**
43
 * A class to easily create a connection to a Solr server.
44
 *
45
 * Internally keeps track of already existing connections and makes sure that no
46
 * duplicate connections are created.
47
 *
48
 * @author Ingo Renner <[email protected]>
49
 */
50
class ConnectionManager implements SingletonInterface, ClearCacheActionsHookInterface
51
{
52
53
    /**
54
     * @var array
55
     */
56
    protected static $connections = [];
57
58
    /**
59
     * @var \ApacheSolrForTypo3\Solr\System\Records\SystemLanguage\SystemLanguageRepository
60
     */
61
    protected $systemLanguageRepository;
62
63
    /**
64
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
65
     */
66
    protected $logger = null;
67
68
    /**
69
     * @var PagesRepositoryAtExtSolr
70
     */
71
    protected $pagesRepositoryAtExtSolr;
72
73
    /**
74
     * @param SystemLanguageRepository $systemLanguageRepository
75
     * @param PagesRepositoryAtExtSolr|null $pagesRepositoryAtExtSolr
76
     * @param SolrLogManager $solrLogManager
77
     */
78 118
    public function __construct(SystemLanguageRepository $systemLanguageRepository = null, PagesRepositoryAtExtSolr $pagesRepositoryAtExtSolr = null, SolrLogManager $solrLogManager = null)
79
    {
80 118
        $this->systemLanguageRepository = $systemLanguageRepository ?? GeneralUtility::makeInstance(SystemLanguageRepository::class);
81 118
        $this->pagesRepositoryAtExtSolr = $pagesRepositoryAtExtSolr ?? GeneralUtility::makeInstance(PagesRepositoryAtExtSolr::class);
82 118
        $this->logger                   = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
83 118
    }
84
85
    /**
86
     * Gets a Solr connection.
87
     *
88
     * Instead of generating a new connection with each call, connections are
89
     * kept and checked whether the requested connection already exists. If a
90
     * connection already exists, it's reused.
91
     *
92
     * @deprecated This method can only be used to build a connection with the same endpoint for reading, writing and admin operations,
93
     * if you need a connection to the different endpoints, please use getConnectionByPageId()
94
     * @param string $host Solr host (optional)
95
     * @param int $port Solr port (optional)
96
     * @param string $path Solr path (optional)
97
     * @param string $scheme Solr scheme, defaults to http, can be https (optional)
98
     * @param string $username Solr user name (optional)
99
     * @param string $password Solr password (optional)
100
     * @param int $timeout
101
     * @return SolrConnection A solr connection.
102
     */
103
    public function getConnection($host = '', $port = 8983, $path = '/solr/', $scheme = 'http', $username = '', $password = '', $timeout = 0)
104
    {
105
        trigger_error('ConnectionManager::getConnection is deprecated please use getSolrConnectionForNodes now.', E_USER_DEPRECATED);
106
        if (empty($host)) {
107
            throw new \InvalidArgumentException('Host argument should not be empty');
108
        }
109
110
        $readNode = ['scheme' => $scheme, 'host' => $host, 'port' => $port, 'path' => $path, 'username' => $username, 'password' => $password, 'timeout' => $timeout];
111
        $writeNode = ['scheme' => $scheme, 'host' => $host, 'port' => $port, 'path' => $path, 'username' => $username, 'password' => $password, 'timeout' => $timeout];
112
        return $this->getSolrConnectionForNodes($readNode, $writeNode);
113
    }
114
115
    /**
116
     * Creates a solr connection for read and write endpoints
117
     *
118
     * @param array $readNodeConfiguration
119
     * @param array $writeNodeConfiguration
120
     * @return SolrConnection|object
121
     */
122 109
    public function getSolrConnectionForNodes(array $readNodeConfiguration, array $writeNodeConfiguration)
123
    {
124 109
        $connectionHash = md5(\json_encode($readNodeConfiguration) .  \json_encode($writeNodeConfiguration));
125 109
        if (!isset(self::$connections[$connectionHash])) {
126 109
            $readNode = Node::fromArray($readNodeConfiguration);
127 109
            $writeNode = Node::fromArray($writeNodeConfiguration);
128 109
            self::$connections[$connectionHash] = GeneralUtility::makeInstance(SolrConnection::class, $readNode, $writeNode);
129
        }
130 109
        return self::$connections[$connectionHash];
131
    }
132
133
    /**
134
     * Creates a solr configuration from the configuration array and returns it.
135
     *
136
     * @param array $config The solr configuration array
137
     * @return SolrConnection
138
     */
139 109
    public function getConnectionFromConfiguration(array $config)
140
    {
141 109
        if(empty($config['read']) && !empty($config['solrHost'])) {
142
            throw new \InvalidArgumentException('Invalid registry data please re-initialize your solr connections');
143
        }
144
145 109
        return $this->getSolrConnectionForNodes($config['read'], $config['write']);
146
    }
147
148
    /**
149
     * Gets a Solr configuration for a page ID.
150
     *
151
     * @param int $pageId A page ID.
152
     * @param int $language The language ID to get the connection for as the path may differ. Optional, defaults to 0.
153
     * @param string $mount Comma list of MountPoint parameters
154
     * @return array A solr configuration.
155
     * @throws NoSolrConnectionFoundException
156
     */
157 97
    public function getConfigurationByPageId($pageId, $language = 0, $mount = '')
158
    {
159
        // find the root page
160 97
        $pageSelect = GeneralUtility::makeInstance(PageRepository::class);
161
162
        /** @var Rootline $rootLine */
163 97
        $rootLine = GeneralUtility::makeInstance(Rootline::class, /** @scrutinizer ignore-type */ $pageSelect->getRootLine($pageId, $mount));
164 97
        $siteRootPageId = $rootLine->getRootPageId();
165
166
        try {
167 97
            $solrConfiguration = $this->getConfigurationByRootPageId($siteRootPageId, $language);
168 4
        } catch (NoSolrConnectionFoundException $nscfe) {
169
            /* @var $noSolrConnectionException NoSolrConnectionFoundException */
170 4
            $noSolrConnectionException = GeneralUtility::makeInstance(
171 4
                NoSolrConnectionFoundException::class,
172 4
                /** @scrutinizer ignore-type */ $nscfe->getMessage() . ' Initial page used was [' . $pageId . ']',
173 4
                /** @scrutinizer ignore-type */ 1275399922
174
            );
175 4
            $noSolrConnectionException->setPageId($pageId);
176
177 4
            throw $noSolrConnectionException;
178
        }
179
180 94
        return $solrConfiguration;
181
    }
182
183
    /**
184
     * Gets a Solr connection for a page ID.
185
     *
186
     * @param int $pageId A page ID.
187
     * @param int $language The language ID to get the connection for as the path may differ. Optional, defaults to 0.
188
     * @param string $mount Comma list of MountPoint parameters
189
     * @return SolrConnection A solr connection.
190
     * @throws NoSolrConnectionFoundException
191
     */
192 97
    public function getConnectionByPageId($pageId, $language = 0, $mount = '')
193
    {
194 97
        $solrConnections = $this->getConfigurationByPageId($pageId, $language, $mount);
195 94
        $solrConnection = $this->getConnectionFromConfiguration($solrConnections);
196 94
        return $solrConnection;
197
    }
198
199
    /**
200
     * Gets a Solr configuration for a root page ID.
201
     *
202
     * @param int $pageId A root page ID.
203
     * @param int $language The language ID to get the configuration for as the path may differ. Optional, defaults to 0.
204
     * @return array A solr configuration.
205
     * @throws NoSolrConnectionFoundException
206
     */
207 98
    public function getConfigurationByRootPageId($pageId, $language = 0)
208
    {
209 98
        $connectionKey = $pageId . '|' . $language;
210 98
        $solrServers = $this->getAllConfigurations();
211
212 98
        if (isset($solrServers[$connectionKey])) {
213 96
            $solrConfiguration = $solrServers[$connectionKey];
214
        } else {
215
            /* @var $noSolrConnectionException NoSolrConnectionFoundException */
216 5
            $noSolrConnectionException = GeneralUtility::makeInstance(
217 5
                NoSolrConnectionFoundException::class,
218 5
                /** @scrutinizer ignore-type */  'Could not find a Solr connection for root page [' . $pageId . '] and language [' . $language . '].',
219 5
                /** @scrutinizer ignore-type */ 1275396474
220
            );
221 5
            $noSolrConnectionException->setRootPageId($pageId);
222 5
            $noSolrConnectionException->setLanguageId($language);
223
224 5
            throw $noSolrConnectionException;
225
        }
226
227 96
        return $solrConfiguration;
228
    }
229
230
    /**
231
     * Gets a Solr connection for a root page ID.
232
     *
233
     * @param int $pageId A root page ID.
234
     * @param int $language The language ID to get the connection for as the path may differ. Optional, defaults to 0.
235
     * @return SolrConnection A solr connection.
236
     * @throws NoSolrConnectionFoundException
237
     */
238 10
    public function getConnectionByRootPageId($pageId, $language = 0)
239
    {
240 10
        $config = $this->getConfigurationByRootPageId($pageId, $language);
241 10
        $solrConnection = $this->getConnectionFromConfiguration($config);
242
243 10
        return $solrConnection;
244
    }
245
246
    /**
247
     * Gets all connection configurations found.
248
     *
249
     * @return array An array of connection configurations.
250
     */
251 111
    public function getAllConfigurations()
252
    {
253
        /** @var $registry Registry */
254 111
        $registry = GeneralUtility::makeInstance(Registry::class);
255 111
        $solrConfigurations = $registry->get('tx_solr', 'servers', []);
256
257 111
        return $solrConfigurations;
258
    }
259
260
    /**
261
     * Stores the connections in the registry.
262
     *
263
     * @param array $solrConfigurations
264
     */
265 3
    protected function setAllConfigurations(array $solrConfigurations)
266
    {
267
        /** @var $registry Registry */
268 3
        $registry = GeneralUtility::makeInstance(Registry::class);
269 3
        $registry->set('tx_solr', 'servers', $solrConfigurations);
270 3
    }
271
272
    /**
273
     * Gets all connections found.
274
     *
275
     * @return SolrConnection[] An array of initialized ApacheSolrForTypo3\Solr\System\Solr\SolrConnection connections
276
     */
277 7
    public function getAllConnections()
278
    {
279 7
        $solrConnections = [];
280
281 7
        $solrConfigurations = $this->getAllConfigurations();
282 7
        foreach ($solrConfigurations as $solrConfiguration) {
283 7
            $solrConnections[] = $this->getConnectionFromConfiguration($solrConfiguration);
284
        }
285
286 7
        return $solrConnections;
287
    }
288
289
    /**
290
     * Gets all connection configurations for a given site.
291
     *
292
     * @param Site $site A TYPO3 site
293
     * @return array An array of Solr connection configurations for a site
294
     */
295 31
    public function getConfigurationsBySite(Site $site)
296
    {
297 31
        $solrConfigurations = [];
298
299 31
        $allConfigurations = $this->getAllConfigurations();
300 31
        foreach ($allConfigurations as $configuration) {
301 31
            if ($configuration['rootPageUid'] == $site->getRootPageId()) {
302 31
                $solrConfigurations[] = $configuration;
303
            }
304
        }
305
306 31
        return $solrConfigurations;
307
    }
308
309
    /**
310
     * Gets all connections configured for a given site.
311
     *
312
     * @param Site $site A TYPO3 site
313
     * @return SolrConnection[] An array of Solr connection objects (ApacheSolrForTypo3\Solr\System\Solr\SolrConnection)
314
     */
315 16
    public function getConnectionsBySite(Site $site)
316
    {
317 16
        $connections = [];
318
319 16
        $solrServers = $this->getConfigurationsBySite($site);
320 16
        foreach ($solrServers as $solrServer) {
321 16
            $connections[] = $this->getConnectionFromConfiguration($solrServer);
322
        }
323
324 16
        return $connections;
325
    }
326
327
    // updates
328
329
    /**
330
     * Adds a menu entry to the clear cache menu to detect Solr connections.
331
     *
332
     * @deprecated deprecated since 9.0.0 will we removed in 10.0.0 still in place to prevent errors from cached localconf.php
333
     * @todo this method and the implementation of the ClearCacheActionsHookInterface can be removed in EXT:solr 10
334
     * @param array $cacheActions Array of CacheMenuItems
335
     * @param array $optionValues Array of AccessConfigurations-identifiers (typically  used by userTS with options.clearCache.identifier)
336
     */
337
    public function manipulateCacheActions(&$cacheActions, &$optionValues)
338
    {
339
        trigger_error('ConnectionManager::manipulateCacheActions is deprecated please use ClearCacheActionsHook::manipulateCacheActions now', E_USER_DEPRECATED);
340
341
        if ($GLOBALS['BE_USER']->isAdmin()) {
342
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
343
            $optionValues[] = 'clearSolrConnectionCache';
344
            $cacheActions[] = [
345
                'id' => 'clearSolrConnectionCache',
346
                'title' => 'LLL:EXT:solr/Resources/Private/Language/locallang.xlf:cache_initialize_solr_connections',
347
                'href' => $uriBuilder->buildUriFromRoute('ajax_solr_updateConnections'),
348
                'iconIdentifier' => 'extensions-solr-module-initsolrconnections'
349
            ];
350
        }
351
    }
352
353
    /**
354
     * Updates the connections in the registry.
355
     *
356
     */
357 2
    public function updateConnections()
358
    {
359 2
        $solrConnections = $this->getConfiguredSolrConnections();
360 2
        $solrConnections = $this->filterDuplicateConnections($solrConnections);
361
362 2
        if (!empty($solrConnections)) {
363 2
            $this->setAllConfigurations($solrConnections);
364
        }
365 2
    }
366
367
    /**
368
     * Updates the Solr connections for a specific root page ID / site.
369
     *
370
     * @param int $rootPageId A site root page id
371
     */
372 1
    public function updateConnectionByRootPageId($rootPageId)
373
    {
374 1
        $systemLanguages = $this->systemLanguageRepository->findSystemLanguages();
375 1
        $siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
376 1
        $site = $siteRepository->getSiteByRootPageId($rootPageId);
377 1
        $rootPage = $site->getRootPage();
378
379 1
        $updatedSolrConnections = [];
380 1
        foreach ($systemLanguages as $languageId) {
381 1
            $connection = $this->getConfiguredSolrConnectionByRootPage($rootPage, $languageId);
382
383 1
            if (!empty($connection)) {
384 1
                $updatedSolrConnections[$connection['connectionKey']] = $connection;
385
            }
386
        }
387
388 1
        $solrConnections = $this->getAllConfigurations();
389 1
        $solrConnections = array_merge($solrConnections, $updatedSolrConnections);
390 1
        $solrConnections = $this->filterDuplicateConnections($solrConnections);
391 1
        $this->setAllConfigurations($solrConnections);
392 1
    }
393
394
    /**
395
     * Finds the configured Solr connections. Also respects multi-site
396
     * environments.
397
     *
398
     * @return array An array with connections, each connection with keys rootPageTitle, rootPageUid, solrHost, solrPort, solrPath
399
     */
400 2
    protected function getConfiguredSolrConnections()
401
    {
402 2
        $configuredSolrConnections = [];
403
        // find website roots and languages for this installation
404 2
        $rootPages = $this->pagesRepositoryAtExtSolr->findAllRootPages();
405 2
        $languages = $this->systemLanguageRepository->findSystemLanguages();
406
407
        // find solr configurations and add them as function menu entries
408 2
        foreach ($rootPages as $rootPage) {
409 2
            foreach ($languages as $languageId) {
410 2
                $connection = $this->getConfiguredSolrConnectionByRootPage($rootPage, $languageId);
411
412 2
                if (!empty($connection)) {
413 2
                    $configuredSolrConnections[$connection['connectionKey']] = $connection;
414
                }
415
            }
416
        }
417
418 2
        return $configuredSolrConnections;
419
    }
420
421
    /**
422
     * Gets the configured Solr connection for a specific root page and language ID.
423
     *
424
     * @param array $rootPage A root page record with at least title and uid
425
     * @param int $languageId ID of a system language
426
     * @return array A solr connection configuration.
427
     */
428 3
    protected function getConfiguredSolrConnectionByRootPage(array $rootPage, $languageId)
429
    {
430 3
        $connection = [];
431
432 3
        $languageId = (int)$languageId;
433 3
        GeneralUtility::_GETset($languageId, 'L');
434 3
        $connectionKey = $rootPage['uid'] . '|' . $languageId;
435
436 3
        $pageSelect = GeneralUtility::makeInstance(PageRepository::class);
437 3
        $rootLine = $pageSelect->getRootLine($rootPage['uid']);
438
439 3
        $tmpl = GeneralUtility::makeInstance(ExtendedTemplateService::class);
440 3
        $tmpl->tt_track = false; // Do not log time-performance information
441 3
        $tmpl->init();
442 3
        $tmpl->runThroughTemplates($rootLine); // This generates the constants/config + hierarchy info for the template.
443
444
        // fake micro TSFE to get correct condition parsing
445 3
        $GLOBALS['TSFE'] = new \stdClass();
446 3
        $GLOBALS['TSFE']->tmpl = new \stdClass();
447 3
        $GLOBALS['TSFE']->cObjectDepthCounter = 50;
448 3
        $GLOBALS['TSFE']->tmpl->rootLine = $rootLine;
449 3
        $GLOBALS['TSFE']->sys_page = $pageSelect;
450 3
        $GLOBALS['TSFE']->id = $rootPage['uid'];
451 3
        $GLOBALS['TSFE']->page = $rootPage;
452
453 3
        $tmpl->generateConfig();
454 3
        $GLOBALS['TSFE']->tmpl->setup = $tmpl->setup;
455
456 3
        $configuration = Util::getSolrConfigurationFromPageId($rootPage['uid'], false, $languageId);
457
458 3
        $solrIsEnabledAndConfigured = $configuration->getEnabled() && $configuration->getSolrHasConnectionConfiguration();
459 3
        if (!$solrIsEnabledAndConfigured) {
460
            return $connection;
461
        }
462
463
        $connection = [
464 3
            'connectionKey' => $connectionKey,
465 3
            'rootPageTitle' => $rootPage['title'],
466 3
            'rootPageUid' => $rootPage['uid'],
467
            'read' => [
468 3
                'scheme' => $configuration->getSolrScheme(),
469 3
                'host' => $configuration->getSolrHost(),
470 3
                'port' => $configuration->getSolrPort(),
471 3
                'path' => $configuration->getSolrPath(),
472 3
                'username' => $configuration->getSolrUsername(),
473 3
                'password' => $configuration->getSolrPassword(),
474 3
                'timeout' => $configuration->getSolrTimeout()
475
            ],
476
            'write' => [
477 3
                'scheme' => $configuration->getSolrScheme('http', 'write'),
478 3
                'host' => $configuration->getSolrHost('localhost', 'write'),
479 3
                'port' => $configuration->getSolrPort(8983, 'write'),
480 3
                'path' => $configuration->getSolrPath('/solr/core_en/', 'write'),
481 3
                'username' => $configuration->getSolrUsername('', 'write'),
482 3
                'password' => $configuration->getSolrPassword('', 'write'),
483 3
                'timeout' => $configuration->getSolrTimeout(0, 'write')
484
            ],
485
486 3
            'language' => $languageId
487
        ];
488
489 3
        $connection['label'] = $this->buildConnectionLabel($connection);
490 3
        return $connection;
491
    }
492
493
494
495
    /**
496
     * Creates a human readable label from the connections' configuration.
497
     *
498
     * @param array $connection Connection configuration
499
     * @return string Connection label
500
     */
501 3
    protected function buildConnectionLabel(array $connection)
502
    {
503 3
        return $connection['rootPageTitle']
504 3
            . ' (pid: ' . $connection['rootPageUid']
505 3
            . ', language: ' . $this->systemLanguageRepository->findOneLanguageTitleByLanguageId($connection['language'])
506 3
            . ') - Read node: '
507 3
            . $connection['read']['host'] . ':'
508 3
            . $connection['read']['port']
509 3
            . $connection['read']['path']
510 3
            .' - Write node: '
511 3
            . $connection['write']['host'] . ':'
512 3
            . $connection['write']['port']
513 3
            . $connection['write']['path'];
514
    }
515
516
    /**
517
     * Filters duplicate connections. When detecting the configured connections
518
     * this is done with a little brute force by simply combining all root pages
519
     * with all languages, this method filters out the duplicates.
520
     *
521
     * @param array $connections An array of unfiltered connections, containing duplicates
522
     * @return array An array with connections, no duplicates.
523
     */
524 3
    protected function filterDuplicateConnections(array $connections)
525
    {
526 3
        $hashedConnections = [];
527 3
        $filteredConnections = [];
528
529
        // array_unique() doesn't work on multi dimensional arrays, so we need to flatten it first
530 3
        foreach ($connections as $key => $connection) {
531 3
            unset($connection['language']);
532 3
            $connectionHash = md5(implode('|', $connection));
533 3
            $hashedConnections[$key] = $connectionHash;
534
        }
535
536 3
        $hashedConnections = array_unique($hashedConnections);
537
538 3
        foreach ($hashedConnections as $key => $hash) {
539 3
            $filteredConnections[$key] = $connections[$key];
540
        }
541
542 3
        return $filteredConnections;
543
    }
544
}
545