Passed
Push — master ( ed38f6...30aafa )
by Timo
11:15
created

ConnectionManager::manipulateCacheActions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 12
ccs 0
cts 10
cp 0
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 6
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 3 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\Site;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, ApacheSolrForTypo3\Solr\Site. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
28
use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
29
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
30
use ApacheSolrForTypo3\Solr\System\Page\Rootline;
31
use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository as PagesRepositoryAtExtSolr;
32
use ApacheSolrForTypo3\Solr\System\Records\SystemLanguage\SystemLanguageRepository;
33
use ApacheSolrForTypo3\Solr\System\Solr\Node;
34
use ApacheSolrForTypo3\Solr\System\Solr\SolrConnection;
35
use TYPO3\CMS\Backend\Routing\UriBuilder;
36
use TYPO3\CMS\Backend\Toolbar\ClearCacheActionsHookInterface;
37
use TYPO3\CMS\Core\Registry;
38
use TYPO3\CMS\Core\SingletonInterface;
39
use TYPO3\CMS\Core\TypoScript\ExtendedTemplateService;
40
use TYPO3\CMS\Core\Utility\GeneralUtility;
41
use TYPO3\CMS\Frontend\Page\PageRepository;
42
43
/**
44
 * A class to easily create a connection to a Solr server.
45
 *
46
 * Internally keeps track of already existing connections and makes sure that no
47
 * duplicate connections are created.
48
 *
49
 * @author Ingo Renner <[email protected]>
50
 */
51
class ConnectionManager implements SingletonInterface
52
{
53
54
    /**
55
     * @var array
56
     */
57
    protected static $connections = [];
58
59
    /**
60
     * @var \ApacheSolrForTypo3\Solr\System\Records\SystemLanguage\SystemLanguageRepository
61
     */
62
    protected $systemLanguageRepository;
63
64
    /**
65
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
66
     */
67
    protected $logger = null;
68
69
    /**
70
     * @var PagesRepositoryAtExtSolr
71
     */
72
    protected $pagesRepositoryAtExtSolr;
73
74
    /**
75
     * @param SystemLanguageRepository $systemLanguageRepository
76
     * @param PagesRepositoryAtExtSolr|null $pagesRepositoryAtExtSolr
77
     * @param SolrLogManager $solrLogManager
78 118
     */
79
    public function __construct(SystemLanguageRepository $systemLanguageRepository = null, PagesRepositoryAtExtSolr $pagesRepositoryAtExtSolr = null, SolrLogManager $solrLogManager = null)
80 118
    {
81 118
        $this->systemLanguageRepository = $systemLanguageRepository ?? GeneralUtility::makeInstance(SystemLanguageRepository::class);
82 118
        $this->pagesRepositoryAtExtSolr = $pagesRepositoryAtExtSolr ?? GeneralUtility::makeInstance(PagesRepositoryAtExtSolr::class);
83 118
        $this->logger                   = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
84
    }
85
86
    /**
87
     * Creates a solr connection for read and write endpoints
88
     *
89
     * @param array $readNodeConfiguration
90
     * @param array $writeNodeConfiguration
91
     * @return SolrConnection|object
92
     */
93
    public function getSolrConnectionForNodes(array $readNodeConfiguration, array $writeNodeConfiguration)
94
    {
95
        $connectionHash = md5(\json_encode($readNodeConfiguration) .  \json_encode($writeNodeConfiguration));
96
        if (!isset(self::$connections[$connectionHash])) {
97
            $readNode = Node::fromArray($readNodeConfiguration);
98
            $writeNode = Node::fromArray($writeNodeConfiguration);
99
            self::$connections[$connectionHash] = GeneralUtility::makeInstance(SolrConnection::class, $readNode, $writeNode);
100
        }
101
        return self::$connections[$connectionHash];
102
    }
103
104
    /**
105
     * Creates a solr configuration from the configuration array and returns it.
106
     *
107
     * @param array $config The solr configuration array
108
     * @return SolrConnection
109
     */
110
    public function getConnectionFromConfiguration(array $config)
111
    {
112
        if(empty($config['read']) && !empty($config['solrHost'])) {
113
            throw new \InvalidArgumentException('Invalid registry data please re-initialize your solr connections');
114
        }
115
116
        return $this->getSolrConnectionForNodes($config['read'], $config['write']);
117
    }
118
119
    /**
120
     * Gets a Solr configuration for a page ID.
121
     *
122 109
     * @param int $pageId A page ID.
123
     * @param int $language The language ID to get the connection for as the path may differ. Optional, defaults to 0.
124 109
     * @param string $mount Comma list of MountPoint parameters
125 109
     * @return array A solr configuration.
126 109
     * @throws NoSolrConnectionFoundException
127 109
     */
128 109
    public function getConfigurationByPageId($pageId, $language = 0, $mount = '')
129
    {
130 109
        // find the root page
131
        $pageSelect = GeneralUtility::makeInstance(PageRepository::class);
132
133
        /** @var Rootline $rootLine */
134
        $rootLine = GeneralUtility::makeInstance(Rootline::class, /** @scrutinizer ignore-type */ $pageSelect->getRootLine($pageId, $mount));
135
        $siteRootPageId = $rootLine->getRootPageId();
136
137
        try {
138
            $solrConfiguration = $this->getConfigurationByRootPageId($siteRootPageId, $language);
139 109
        } catch (NoSolrConnectionFoundException $nscfe) {
140
            /* @var $noSolrConnectionException NoSolrConnectionFoundException */
141 109
            $noSolrConnectionException = GeneralUtility::makeInstance(
142
                NoSolrConnectionFoundException::class,
143
                /** @scrutinizer ignore-type */ $nscfe->getMessage() . ' Initial page used was [' . $pageId . ']',
144
                /** @scrutinizer ignore-type */ 1275399922
145 109
            );
146
            $noSolrConnectionException->setPageId($pageId);
147
148
            throw $noSolrConnectionException;
149
        }
150
151
        return $solrConfiguration;
152
    }
153
154
    /**
155
     * Gets a Solr connection for a page ID.
156
     *
157 97
     * @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 97
     * @return SolrConnection A solr connection.
161
     * @throws NoSolrConnectionFoundException
162
     */
163 97
    public function getConnectionByPageId($pageId, $language = 0, $mount = '')
164 97
    {
165
        $solrConnections = $this->getConfigurationByPageId($pageId, $language, $mount);
166
        $solrConnection = $this->getConnectionFromConfiguration($solrConnections);
167 97
        return $solrConnection;
168 4
    }
169
170 4
    /**
171 4
     * Gets a Solr configuration for a root page ID.
172 4
     *
173 4
     * @param int $pageId A root page ID.
174
     * @param int $language The language ID to get the configuration for as the path may differ. Optional, defaults to 0.
175 4
     * @return array A solr configuration.
176
     * @throws NoSolrConnectionFoundException
177 4
     */
178
    public function getConfigurationByRootPageId($pageId, $language = 0)
179
    {
180 94
        $connectionKey = $pageId . '|' . $language;
181
        $solrServers = $this->getAllConfigurations();
182
183
        if (isset($solrServers[$connectionKey])) {
184
            $solrConfiguration = $solrServers[$connectionKey];
185
        } else {
186
            /* @var $noSolrConnectionException NoSolrConnectionFoundException */
187
            $noSolrConnectionException = GeneralUtility::makeInstance(
188
                NoSolrConnectionFoundException::class,
189
                /** @scrutinizer ignore-type */  'Could not find a Solr connection for root page [' . $pageId . '] and language [' . $language . '].',
190
                /** @scrutinizer ignore-type */ 1275396474
191
            );
192 97
            $noSolrConnectionException->setRootPageId($pageId);
193
            $noSolrConnectionException->setLanguageId($language);
194 97
195 94
            throw $noSolrConnectionException;
196 94
        }
197
198
        return $solrConfiguration;
199
    }
200
201
    /**
202
     * Gets a Solr connection for a root page ID.
203
     *
204
     * @param int $pageId A root page ID.
205
     * @param int $language The language ID to get the connection for as the path may differ. Optional, defaults to 0.
206
     * @return SolrConnection A solr connection.
207 98
     * @throws NoSolrConnectionFoundException
208
     */
209 98
    public function getConnectionByRootPageId($pageId, $language = 0)
210 98
    {
211
        $config = $this->getConfigurationByRootPageId($pageId, $language);
212 98
        $solrConnection = $this->getConnectionFromConfiguration($config);
213 96
214
        return $solrConnection;
215
    }
216 5
217 5
    /**
218 5
     * Gets all connection configurations found.
219 5
     *
220
     * @return array An array of connection configurations.
221 5
     */
222 5
    public function getAllConfigurations()
223
    {
224 5
        /** @var $registry Registry */
225
        $registry = GeneralUtility::makeInstance(Registry::class);
226
        $solrConfigurations = $registry->get('tx_solr', 'servers', []);
227 96
228
        return $solrConfigurations;
229
    }
230
231
    /**
232
     * Stores the connections in the registry.
233
     *
234
     * @param array $solrConfigurations
235
     */
236
    protected function setAllConfigurations(array $solrConfigurations)
237
    {
238 10
        /** @var $registry Registry */
239
        $registry = GeneralUtility::makeInstance(Registry::class);
240 10
        $registry->set('tx_solr', 'servers', $solrConfigurations);
241 10
    }
242
243 10
    /**
244
     * Gets all connections found.
245
     *
246
     * @return SolrConnection[] An array of initialized ApacheSolrForTypo3\Solr\System\Solr\SolrConnection connections
247
     */
248
    public function getAllConnections()
249
    {
250
        $solrConnections = [];
251 111
252
        $solrConfigurations = $this->getAllConfigurations();
253
        foreach ($solrConfigurations as $solrConfiguration) {
254 111
            $solrConnections[] = $this->getConnectionFromConfiguration($solrConfiguration);
255 111
        }
256
257 111
        return $solrConnections;
258
    }
259
260
    /**
261
     * Gets all connection configurations for a given site.
262
     *
263
     * @param Site $site A TYPO3 site
264
     * @return array An array of Solr connection configurations for a site
265 3
     */
266
    public function getConfigurationsBySite(Site $site)
267
    {
268 3
        $solrConfigurations = [];
269 3
270 3
        $allConfigurations = $this->getAllConfigurations();
271
        foreach ($allConfigurations as $configuration) {
272
            if ($configuration['rootPageUid'] == $site->getRootPageId()) {
273
                $solrConfigurations[] = $configuration;
274
            }
275
        }
276
277 7
        return $solrConfigurations;
278
    }
279 7
280
    /**
281 7
     * Gets all connections configured for a given site.
282 7
     *
283 7
     * @param Site $site A TYPO3 site
284
     * @return SolrConnection[] An array of Solr connection objects (ApacheSolrForTypo3\Solr\System\Solr\SolrConnection)
285
     */
286 7
    public function getConnectionsBySite(Site $site)
287
    {
288
        $connections = [];
289
290
        $solrServers = $this->getConfigurationsBySite($site);
291
        foreach ($solrServers as $solrServer) {
292
            $connections[] = $this->getConnectionFromConfiguration($solrServer);
293
        }
294
295 31
        return $connections;
296
    }
297 31
298
    // updates
299 31
300 31
    /**
301 31
     * Updates the connections in the registry.
302 31
     *
303
     */
304
    public function updateConnections()
305
    {
306 31
        $solrConnections = $this->getConfiguredSolrConnections();
307
        $solrConnections = $this->filterDuplicateConnections($solrConnections);
308
309
        if (!empty($solrConnections)) {
310
            $this->setAllConfigurations($solrConnections);
311
        }
312
    }
313
314
    /**
315 16
     * Updates the Solr connections for a specific root page ID / site.
316
     *
317 16
     * @param int $rootPageId A site root page id
318
     */
319 16
    public function updateConnectionByRootPageId($rootPageId)
320 16
    {
321 16
        $systemLanguages = $this->systemLanguageRepository->findSystemLanguages();
322
        $siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
323
        $site = $siteRepository->getSiteByRootPageId($rootPageId);
324 16
        $rootPage = $site->getRootPage();
325
326
        $updatedSolrConnections = [];
327
        foreach ($systemLanguages as $languageId) {
328
            $connection = $this->getConfiguredSolrConnectionByRootPage($rootPage, $languageId);
329
330
            if (!empty($connection)) {
331
                $updatedSolrConnections[$connection['connectionKey']] = $connection;
332
            }
333
        }
334
335
        $solrConnections = $this->getAllConfigurations();
336
        $solrConnections = array_merge($solrConnections, $updatedSolrConnections);
337
        $solrConnections = $this->filterDuplicateConnections($solrConnections);
338
        $this->setAllConfigurations($solrConnections);
339
    }
340
341
    /**
342
     * Finds the configured Solr connections. Also respects multi-site
343
     * environments.
344
     *
345
     * @return array An array with connections, each connection with keys rootPageTitle, rootPageUid, solrHost, solrPort, solrPath
346
     */
347
    protected function getConfiguredSolrConnections()
348
    {
349
        $configuredSolrConnections = [];
350
        // find website roots and languages for this installation
351
        $rootPages = $this->pagesRepositoryAtExtSolr->findAllRootPages();
352
        $languages = $this->systemLanguageRepository->findSystemLanguages();
353
354
        // find solr configurations and add them as function menu entries
355
        foreach ($rootPages as $rootPage) {
356
            foreach ($languages as $languageId) {
357 2
                $connection = $this->getConfiguredSolrConnectionByRootPage($rootPage, $languageId);
358
359 2
                if (!empty($connection)) {
360 2
                    $configuredSolrConnections[$connection['connectionKey']] = $connection;
361
                }
362 2
            }
363 2
        }
364
365 2
        return $configuredSolrConnections;
366
    }
367
368
    /**
369
     * Gets the configured Solr connection for a specific root page and language ID.
370
     *
371
     * @param array $rootPage A root page record with at least title and uid
372 1
     * @param int $languageId ID of a system language
373
     * @return array A solr connection configuration.
374 1
     */
375 1
    protected function getConfiguredSolrConnectionByRootPage(array $rootPage, $languageId)
376 1
    {
377 1
        $connection = [];
378
379 1
        $languageId = (int)$languageId;
380 1
        GeneralUtility::_GETset($languageId, 'L');
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Core\Utility\GeneralUtility::_GETset() has been deprecated: since TYPO3 v9 LTS, will be removed in TYPO3 v10.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

380
        /** @scrutinizer ignore-deprecated */ GeneralUtility::_GETset($languageId, 'L');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
381 1
        $connectionKey = $rootPage['uid'] . '|' . $languageId;
382
383 1
        $pageSelect = GeneralUtility::makeInstance(PageRepository::class);
384 1
        $rootLine = $pageSelect->getRootLine($rootPage['uid']);
385
386
        $tmpl = GeneralUtility::makeInstance(ExtendedTemplateService::class);
387
        $tmpl->tt_track = false; // Do not log time-performance information
388 1
        $tmpl->init();
389 1
        $tmpl->runThroughTemplates($rootLine); // This generates the constants/config + hierarchy info for the template.
390 1
391 1
        // fake micro TSFE to get correct condition parsing
392 1
        $GLOBALS['TSFE'] = new \stdClass();
393
        $GLOBALS['TSFE']->tmpl = new \stdClass();
394
        $GLOBALS['TSFE']->cObjectDepthCounter = 50;
395
        $GLOBALS['TSFE']->tmpl->rootLine = $rootLine;
396
        $GLOBALS['TSFE']->sys_page = $pageSelect;
397
        $GLOBALS['TSFE']->id = $rootPage['uid'];
398
        $GLOBALS['TSFE']->page = $rootPage;
399
400 2
        $tmpl->generateConfig();
401
        $GLOBALS['TSFE']->tmpl->setup = $tmpl->setup;
402 2
403
        $configuration = Util::getSolrConfigurationFromPageId($rootPage['uid'], false, $languageId);
404 2
405 2
        $solrIsEnabledAndConfigured = $configuration->getEnabled() && $configuration->getSolrHasConnectionConfiguration();
406
        if (!$solrIsEnabledAndConfigured) {
407
            return $connection;
408 2
        }
409 2
410 2
        $connection = [
411
            'connectionKey' => $connectionKey,
412 2
            'rootPageTitle' => $rootPage['title'],
413 2
            'rootPageUid' => $rootPage['uid'],
414
            'read' => [
415
                'scheme' => $configuration->getSolrScheme(),
416
                'host' => $configuration->getSolrHost(),
417
                'port' => $configuration->getSolrPort(),
418 2
                'path' => $configuration->getSolrPath(),
419
                'username' => $configuration->getSolrUsername(),
420
                'password' => $configuration->getSolrPassword(),
421
                'timeout' => $configuration->getSolrTimeout()
422
            ],
423
            'write' => [
424
                'scheme' => $configuration->getSolrScheme('http', 'write'),
425
                'host' => $configuration->getSolrHost('localhost', 'write'),
426
                'port' => $configuration->getSolrPort(8983, 'write'),
427
                'path' => $configuration->getSolrPath('/solr/core_en/', 'write'),
428 3
                'username' => $configuration->getSolrUsername('', 'write'),
429
                'password' => $configuration->getSolrPassword('', 'write'),
430 3
                'timeout' => $configuration->getSolrTimeout(0, 'write')
431
            ],
432 3
433 3
            'language' => $languageId
434 3
        ];
435
436 3
        $connection['label'] = $this->buildConnectionLabel($connection);
437 3
        return $connection;
438
    }
439 3
440 3
441 3
442 3
    /**
443
     * Creates a human readable label from the connections' configuration.
444
     *
445 3
     * @param array $connection Connection configuration
446 3
     * @return string Connection label
447 3
     */
448 3
    protected function buildConnectionLabel(array $connection)
449 3
    {
450 3
        return $connection['rootPageTitle']
451 3
            . ' (pid: ' . $connection['rootPageUid']
452
            . ', language: ' . $this->systemLanguageRepository->findOneLanguageTitleByLanguageId($connection['language'])
453 3
            . ') - Read node: '
454 3
            . $connection['read']['host'] . ':'
455
            . $connection['read']['port']
456 3
            . $connection['read']['path']
457
            .' - Write node: '
458 3
            . $connection['write']['host'] . ':'
459 3
            . $connection['write']['port']
460
            . $connection['write']['path'];
461
    }
462
463
    /**
464 3
     * Filters duplicate connections. When detecting the configured connections
465 3
     * this is done with a little brute force by simply combining all root pages
466 3
     * with all languages, this method filters out the duplicates.
467
     *
468 3
     * @param array $connections An array of unfiltered connections, containing duplicates
469 3
     * @return array An array with connections, no duplicates.
470 3
     */
471 3
    protected function filterDuplicateConnections(array $connections)
472 3
    {
473 3
        $hashedConnections = [];
474 3
        $filteredConnections = [];
475
476
        // array_unique() doesn't work on multi dimensional arrays, so we need to flatten it first
477 3
        foreach ($connections as $key => $connection) {
478 3
            unset($connection['language']);
479 3
            $connectionHash = md5(implode('|', $connection));
480 3
            $hashedConnections[$key] = $connectionHash;
481 3
        }
482 3
483 3
        $hashedConnections = array_unique($hashedConnections);
484
485
        foreach ($hashedConnections as $key => $hash) {
486 3
            $filteredConnections[$key] = $connections[$key];
487
        }
488
489 3
        return $filteredConnections;
490 3
    }
491
}
492