Passed
Push — master ( f63a3e...201a83 )
by Timo
34:43
created

ConnectionManager::getConnectionByRootPageId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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