Completed
Pull Request — master (#840)
by Timo
13:12
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\NoSolrConnectionFoundException;
28
use ApacheSolrForTypo3\Solr\Site;
29
use ApacheSolrForTypo3\Solr\SolrService;
30
use ApacheSolrForTypo3\Solr\System\Page\Rootline;
31
use TYPO3\CMS\Backend\Routing\UriBuilder;
32
use TYPO3\CMS\Backend\Toolbar\ClearCacheActionsHookInterface;
33
use TYPO3\CMS\Core\Imaging\Icon;
34
use TYPO3\CMS\Core\Imaging\IconFactory;
35
use TYPO3\CMS\Core\Registry;
36
use TYPO3\CMS\Core\SingletonInterface;
37
use TYPO3\CMS\Core\TypoScript\ExtendedTemplateService;
38
use TYPO3\CMS\Core\Utility\GeneralUtility;
39
use TYPO3\CMS\Frontend\Page\PageRepository;
40
41
/**
42
 * A class to easily create a connection to a Solr server.
43
 *
44
 * Internally keeps track of already existing connections and makes sure that no
45
 * duplicate connections are created.
46
 *
47
 * @author Ingo Renner <[email protected]>
48
 */
49
class ConnectionManager implements SingletonInterface, ClearCacheActionsHookInterface
50
{
51
52
    /**
53
     * @var array
54
     */
55
    protected static $connections = [];
56
57
    /**
58
     * Gets a Solr connection.
59
     *
60
     * Instead of generating a new connection with each call, connections are
61
     * kept and checked whether the requested connection already exists. If a
62
     * connection already exists, it's reused.
63
     *
64
     * @param string $host Solr host (optional)
65
     * @param int $port Solr port (optional)
66
     * @param string $path Solr path (optional)
67
     * @param string $scheme Solr scheme, defaults to http, can be https (optional)
68
     * @param string $username Solr user name (optional)
69
     * @param string $password Solr password (optional)
70
     * @return SolrService A solr connection.
71
     */
72
73 57
    public function getConnection($host = '', $port = 8983, $path = '/solr/', $scheme = 'http', $username = '', $password = '')
74
    {
75 57
        $connection = null;
0 ignored issues
show
Unused Code introduced by
$connection is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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