Completed
Push — master ( f2d6e9...b6358f )
by Rafael
34:00
created

ConnectionManager::getConfiguredSolrConnections()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

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