Passed
Push — master ( c009f5...1fa0ee )
by Timo
02:35
created

ConnectionManager::getAllConnections()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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