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