Passed
Push — master ( 0b1d58...abb35c )
by Timo
55s
created

ConnectionManager::getLanguageName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.0067

Importance

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