Completed
Branch master (b9fc31)
by Timo
05:19
created

ConnectionManager::updateConnectionByRootPageId()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3

Importance

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