Passed
Push — master ( e55157...fac06d )
by Timo
22:34
created

ConnectionManager::updateConnections()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
516 3
            . $connection['solrHost'] . ':'
517 3
            . $connection['solrPort']
518 3
            . $connection['solrPath'];
519
    }
520
521
    /**
522
     * Filters duplicate connections. When detecting the configured connections
523
     * this is done with a little brute force by simply combining all root pages
524
     * with all languages, this method filters out the duplicates.
525
     *
526
     * @param array $connections An array of unfiltered connections, containing duplicates
527
     * @return array An array with connections, no duplicates.
528
     */
529 3
    protected function filterDuplicateConnections(array $connections)
530
    {
531 3
        $hashedConnections = [];
532 3
        $filteredConnections = [];
533
534
        // array_unique() doesn't work on multi dimensional arrays, so we need to flatten it first
535 3
        foreach ($connections as $key => $connection) {
536 3
            unset($connection['language']);
537 3
            $connectionHash = md5(implode('|', $connection));
538 3
            $hashedConnections[$key] = $connectionHash;
539
        }
540
541 3
        $hashedConnections = array_unique($hashedConnections);
542
543 3
        foreach ($hashedConnections as $key => $hash) {
544 3
            $filteredConnections[$key] = $connections[$key];
545
        }
546
547 3
        return $filteredConnections;
548
    }
549
}
550