Passed
Push — master ( d3e80f...0c7120 )
by Timo
04:52
created

IndexService   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 284
Duplicated Lines 0 %

Test Coverage

Coverage 80.23%

Importance

Changes 0
Metric Value
wmc 27
eloc 88
dl 0
loc 284
ccs 69
cts 86
cp 0.8023
rs 10
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A generateIndexingErrorLog() 0 9 1
A emitSignal() 0 3 1
A __construct() 0 7 1
B indexItems() 0 39 7
A getContextTask() 0 3 1
A setContextTask() 0 3 1
A getIndexerByItem() 0 14 2
A indexItem() 0 31 6
A initializeHttpServerEnvironment() 0 15 2
A getProgress() 0 3 1
A getFailCount() 0 3 1
A getHostByRootPageId() 0 5 1
A restoreOriginalHttpHost() 0 10 2
1
<?php
2
3
namespace ApacheSolrForTypo3\Solr\Domain\Index;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2015-2016 Timo Hund <[email protected]>
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 3 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use ApacheSolrForTypo3\Solr\ConnectionManager;
29
use ApacheSolrForTypo3\Solr\IndexQueue\Indexer;
30
use ApacheSolrForTypo3\Solr\IndexQueue\Item;
31
use ApacheSolrForTypo3\Solr\IndexQueue\Queue;
32
use ApacheSolrForTypo3\Solr\Domain\Site\Site;
33
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
34
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
35
use ApacheSolrForTypo3\Solr\Task\IndexQueueWorkerTask;
36
use Solarium\Exception\HttpException;
37
use TYPO3\CMS\Backend\Utility\BackendUtility;
38
use TYPO3\CMS\Core\Utility\GeneralUtility;
39
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
40
41
/**
42
 * Service to perform indexing operations
43
 *
44
 * @author Timo Hund <[email protected]>
45
 */
46
class IndexService
47
{
48
    /**
49
     * @var TypoScriptConfiguration
50
     */
51
    protected $configuration;
52
53
    /**
54
     * @var Site
55
     */
56
    protected $site;
57
58
    /**
59
     * @var IndexQueueWorkerTask
60
     */
61
    protected $contextTask;
62
63
    /**
64
     * @var Queue
65
     */
66
    protected $indexQueue;
67
68
    /**
69
     * @var Dispatcher
70
     */
71
    protected $signalSlotDispatcher;
72
73
    /**
74
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
75
     */
76
    protected $logger = null;
77
78
    /**
79
     * IndexService constructor.
80
     * @param Site $site
81
     * @param Queue|null $queue
82
     * @param Dispatcher|null $dispatcher
83
     * @param SolrLogManager|null $solrLogManager
84
     */
85 7
    public function __construct(Site $site, Queue $queue = null, Dispatcher $dispatcher = null, SolrLogManager $solrLogManager = null)
86
    {
87 7
        $this->site = $site;
88 7
        $this->indexQueue = $queue ?? GeneralUtility::makeInstance(Queue::class);
89 7
        $this->signalSlotDispatcher = $dispatcher ?? GeneralUtility::makeInstance(Dispatcher::class);
90 7
        $this->logger = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
91 7
        define('EXT_SOLR_INDEXING_CONTEXT', true);
92 7
    }
93
94
    /**
95
     * @param \ApacheSolrForTypo3\Solr\Task\IndexQueueWorkerTask $contextTask
96
     */
97 1
    public function setContextTask($contextTask)
98
    {
99 1
        $this->contextTask = $contextTask;
100 1
    }
101
102
    /**
103
     * @return \ApacheSolrForTypo3\Solr\Task\IndexQueueWorkerTask
104
     */
105 5
    public function getContextTask()
106
    {
107 5
        return $this->contextTask;
108
    }
109
110
    /**
111
     * Indexes items from the Index Queue.
112
     *
113
     * @param int $limit
114
     * @return bool
115
     */
116 5
    public function indexItems($limit)
117
    {
118 5
        $errors     = 0;
119 5
        $indexRunId = uniqid();
120 5
        $configurationToUse = $this->site->getSolrConfiguration();
121 5
        $enableCommitsSetting = $configurationToUse->getEnableCommits();
122
123
        // get items to index
124 5
        $itemsToIndex = $this->indexQueue->getItemsToIndex($this->site, $limit);
125
126 5
        $this->emitSignal('beforeIndexItems', [$itemsToIndex, $this->getContextTask(), $indexRunId]);
127
128 5
        foreach ($itemsToIndex as $itemToIndex) {
129
            try {
130
                // try indexing
131 5
                $this->emitSignal('beforeIndexItem', [$itemToIndex, $this->getContextTask(), $indexRunId]);
132 5
                $this->indexItem($itemToIndex, $configurationToUse);
133 5
                $this->emitSignal('afterIndexItem', [$itemToIndex, $this->getContextTask(), $indexRunId]);
134
            } catch (\Exception $e) {
135
                $errors++;
136
                $this->indexQueue->markItemAsFailed($itemToIndex, $e->getCode() . ': ' . $e->__toString());
137 5
                $this->generateIndexingErrorLog($itemToIndex, $e);
138
            }
139
        }
140
141 5
        $this->emitSignal('afterIndexItems', [$itemsToIndex, $this->getContextTask(), $indexRunId]);
142
143 5
        if ($enableCommitsSetting && count($itemsToIndex) > 0) {
144 4
            $solrServers = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionsBySite($this->site);
145 4
            foreach ($solrServers as $solrServer) {
146
                try {
147 4
                    $solrServer->getWriteService()->commit(false, false, false);
148
                } catch (HttpException $e) {
149 4
                    $errors++;
150
                }
151
            }
152
        }
153
154 5
        return ($errors === 0);
155
    }
156
157
    /**
158
     * Generates a message in the error log when an error occured.
159
     *
160
     * @param Item $itemToIndex
161
     * @param \Exception  $e
162
     */
163
    protected function generateIndexingErrorLog(Item $itemToIndex, \Exception $e)
164
    {
165
        $message = 'Failed indexing Index Queue item ' . $itemToIndex->getIndexQueueUid();
166
        $data = ['code' => $e->getCode(), 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'item' => (array)$itemToIndex];
167
168
        $this->logger->log(
169
            SolrLogManager::ERROR,
170
            $message,
171
            $data
172
        );
173
    }
174
175
    /**
176
     * Builds an emits a singal for the IndexService.
177
     *
178
     * @param string $name
179
     * @param array $arguments
180
     * @return mixed
181
     */
182 5
    protected function emitSignal($name, $arguments)
183
    {
184 5
        return $this->signalSlotDispatcher->dispatch(__CLASS__, $name, $arguments);
185
    }
186
187
    /**
188
     * Indexes an item from the Index Queue.
189
     *
190
     * @param Item $item An index queue item to index
191
     * @param TypoScriptConfiguration $configuration
192
     * @return bool TRUE if the item was successfully indexed, FALSE otherwise
193
     */
194 4
    protected function indexItem(Item $item, TypoScriptConfiguration $configuration)
195
    {
196 4
        $indexer = $this->getIndexerByItem($item->getIndexingConfigurationName(), $configuration);
197
198
        // Remember original http host value
199 4
        $originalHttpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
200
201 4
        $itemChangedDate = $item->getChanged();
202 4
        $itemChangedDateAfterIndex = 0;
203
204 4
        try {
205 4
            $this->initializeHttpServerEnvironment($item);
206
            $itemIndexed = $indexer->index($item);
207
208 4
            // update IQ item so that the IQ can determine what's been indexed already
209 4
            if ($itemIndexed) {
210 4
                $this->indexQueue->updateIndexTimeByItem($item);
211
                $itemChangedDateAfterIndex = $item->getChanged();
212
            }
213 4
214
            if ($itemChangedDateAfterIndex > $itemChangedDate && $itemChangedDateAfterIndex > time()) {
215
                $this->indexQueue->setForcedChangeTimeByItem($item, $itemChangedDateAfterIndex);
216
            }
217 4
        } catch (\Exception $e) {
218
            $this->restoreOriginalHttpHost($originalHttpHost);
219
            throw $e;
220 4
        }
221
222
        $this->restoreOriginalHttpHost($originalHttpHost);
223
224 4
        return $itemIndexed;
225
    }
226 4
227
    /**
228
     * A factory method to get an indexer depending on an item's configuration.
229
     *
230
     * By default all items are indexed using the default indexer
231
     * (ApacheSolrForTypo3\Solr\IndexQueue\Indexer) coming with EXT:solr. Pages by default are
232
     * configured to be indexed through a dedicated indexer
233
     * (ApacheSolrForTypo3\Solr\IndexQueue\PageIndexer). In all other cases a dedicated indexer
234
     * can be specified through TypoScript if needed.
235
     *
236
     * @param string $indexingConfigurationName Indexing configuration name.
237
     * @param TypoScriptConfiguration $configuration
238
     * @return Indexer
239
     */
240
    protected function getIndexerByItem($indexingConfigurationName, TypoScriptConfiguration $configuration)
241
    {
242 4
        $indexerClass = $configuration->getIndexQueueIndexerByConfigurationName($indexingConfigurationName);
243
        $indexerConfiguration = $configuration->getIndexQueueIndexerConfigurationByConfigurationName($indexingConfigurationName);
244 4
245 4
        $indexer = GeneralUtility::makeInstance($indexerClass, /** @scrutinizer ignore-type */ $indexerConfiguration);
246
        if (!($indexer instanceof Indexer)) {
247 4
            throw new \RuntimeException(
248 4
                'The indexer class "' . $indexerClass . '" for indexing configuration "' . $indexingConfigurationName . '" is not a valid indexer. Must be a subclass of ApacheSolrForTypo3\Solr\IndexQueue\Indexer.',
249
                1260463206
250
            );
251
        }
252
253
        return $indexer;
254
    }
255 4
256
    /**
257
     * Gets the indexing progress.
258
     *
259
     * @return float Indexing progress as a two decimal precision float. f.e. 44.87
260
     */
261
    public function getProgress()
262
    {
263 1
        return $this->indexQueue->getStatisticsBySite($this->site)->getSuccessPercentage();
264
    }
265 1
266
    /**
267
     * Returns the amount of failed queue items for the current site.
268
     *
269
     * @return int
270
     */
271
    public function getFailCount()
272
    {
273 1
        return $this->indexQueue->getStatisticsBySite($this->site)->getFailedCount();
274
    }
275 1
276
    /**
277
     * Initializes the $_SERVER['HTTP_HOST'] environment variable in CLI
278
     * environments dependent on the Index Queue item's root page.
279
     *
280
     * When the Index Queue Worker task is executed by a cron job there is no
281
     * HTTP_HOST since we are in a CLI environment. RealURL needs the host
282
     * information to generate a proper URL though. Using the Index Queue item's
283
     * root page information we can determine the correct host although being
284
     * in a CLI environment.
285
     *
286
     * @param Item $item Index Queue item to use to determine the host.
287
     * @param
288
     */
289
    protected function initializeHttpServerEnvironment(Item $item)
290
    {
291 4
        static $hosts = [];
292
        $rootpageId = $item->getRootPageUid();
293 4
        $hostFound = !empty($hosts[$rootpageId]);
294 4
295 4
        if (!$hostFound) {
296
            $host = $this->getHostByRootPageId($rootpageId);
297 4
            $hosts[$rootpageId] = $host;
298 4
        }
299 4
300 4
        $_SERVER['HTTP_HOST'] = $hosts[$rootpageId];
301
302
        // needed since TYPO3 7.5
303 4
        GeneralUtility::flushInternalRuntimeCaches();
304
    }
305
306 4
    /**
307 4
     * @param string|null $originalHttpHost
308
     */
309
    protected function restoreOriginalHttpHost($originalHttpHost)
310
    {
311
        if (!is_null($originalHttpHost)) {
312
            $_SERVER['HTTP_HOST'] = $originalHttpHost;
313
        } else {
314
            unset($_SERVER['HTTP_HOST']);
315
        }
316
317
        // needed since TYPO3 7.5
318
        GeneralUtility::flushInternalRuntimeCaches();
319
    }
320
321
    /**
322
     * @param $rootpageId
323
     * @return string
324
     */
325
    protected function getHostByRootPageId($rootpageId)
326
    {
327
        $rootline = BackendUtility::BEgetRootLine($rootpageId);
328
        $host = BackendUtility::firstDomainRecord($rootline);
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Backend\Utilit...ty::firstDomainRecord() has been deprecated: since TYPO3 v9.4, will be removed in TYPO3 v10.0. Use Link Generation / Router instead. ( Ignorable by Annotation )

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

328
        $host = /** @scrutinizer ignore-deprecated */ BackendUtility::firstDomainRecord($rootline);

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...
329
        return $host;
330
    }
331
}
332