Passed
Push — master ( e55157...ed38f6 )
by Timo
27:41
created

ConnectionManager::getConnection()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 10
ccs 0
cts 7
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 7
crap 6
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');
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Core\Utility\GeneralUtility::_GETset() has been deprecated: since TYPO3 v9 LTS, will be removed in TYPO3 v10.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

433
        /** @scrutinizer ignore-deprecated */ GeneralUtility::_GETset($languageId, 'L');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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