Passed
Push — task/3376-TYPO3_12_compatibili... ( b42ab1...4e0f1e )
by Rafael
49:28 queued 04:28
created

IndexService::emitSignal()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace ApacheSolrForTypo3\Solr\Domain\Index;
17
18
use ApacheSolrForTypo3\Solr\ConnectionManager;
19
use ApacheSolrForTypo3\Solr\Domain\Site\Site;
20
use ApacheSolrForTypo3\Solr\Event\Indexing\AfterIndexItemEvent;
21
use ApacheSolrForTypo3\Solr\Event\Indexing\AfterIndexItemsEvent;
22
use ApacheSolrForTypo3\Solr\Event\Indexing\BeforeIndexItemEvent;
23
use ApacheSolrForTypo3\Solr\Event\Indexing\BeforeIndexItemsEvent;
24
use ApacheSolrForTypo3\Solr\IndexQueue\Indexer;
25
use ApacheSolrForTypo3\Solr\IndexQueue\Item;
26
use ApacheSolrForTypo3\Solr\IndexQueue\Queue;
27
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
28
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
29
use ApacheSolrForTypo3\Solr\Task\IndexQueueWorkerTask;
30
use Doctrine\DBAL\ConnectionException;
31
use Doctrine\DBAL\Driver\Exception;
32
use Psr\EventDispatcher\EventDispatcherInterface;
33
use RuntimeException;
34
use Solarium\Exception\HttpException;
35
use Throwable;
36
use TYPO3\CMS\Core\Utility\GeneralUtility;
37
38
/**
39
 * Service to perform indexing operations
40
 *
41
 * @author Timo Hund <[email protected]>
42
 */
43
class IndexService
44
{
45
    /**
46
     * @var Site
47
     */
48
    protected Site $site;
49
50
    /**
51
     * @var IndexQueueWorkerTask|null
52
     */
53
    protected ?IndexQueueWorkerTask $contextTask = null;
54
55
    /**
56
     * @var Queue
57
     */
58
    protected Queue $indexQueue;
59
60
    /**
61
     * @var EventDispatcherInterface
62
     */
63
    protected EventDispatcherInterface $eventDispatcher;
64
65
    /**
66
     * @var SolrLogManager
67
     */
68
    protected SolrLogManager $logger;
69
70
    /**
71
     * IndexService constructor.
72
     * @param Site $site
73
     * @param Queue|null $queue
74
     * @param EventDispatcherInterface|null $eventDispatcher
75
     * @param SolrLogManager|null $solrLogManager
76
     */
77
    public function __construct(
78
        Site $site,
79
        Queue $queue = null,
80
        EventDispatcherInterface $eventDispatcher = null,
81
        SolrLogManager $solrLogManager = null
82
    ) {
83
        $this->site = $site;
84
        $this->indexQueue = $queue ?? GeneralUtility::makeInstance(Queue::class);
85
        $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::makeInstance(EventDispatcherInterface::class);
86
        $this->logger = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
87
    }
88
89
    /**
90
     * @param IndexQueueWorkerTask $contextTask
91
     */
92
    public function setContextTask(IndexQueueWorkerTask $contextTask)
93
    {
94
        $this->contextTask = $contextTask;
95
    }
96
97
    /**
98
     * @return IndexQueueWorkerTask|null
99
     */
100
    public function getContextTask(): ?IndexQueueWorkerTask
101
    {
102
        return $this->contextTask;
103
    }
104
105
    /**
106
     * Indexes items from the Index Queue.
107
     *
108
     * @param int $limit
109
     * @return bool
110
     * @throws Throwable
111
     * @throws ConnectionException
112
     * @throws Exception
113
     * @throws \Doctrine\DBAL\Exception
114
     */
115
    public function indexItems(int $limit): bool
116
    {
117
        $errors     = 0;
118
        $indexRunId = uniqid();
119
        $configurationToUse = $this->site->getSolrConfiguration();
120
        $enableCommitsSetting = $configurationToUse->getEnableCommits();
121
122
        // get items to index
123
        $itemsToIndex = $this->indexQueue->getItemsToIndex($this->site, $limit);
124
125
        $beforeIndexItemsEvent = new BeforeIndexItemsEvent($itemsToIndex, $this->getContextTask(), $indexRunId);
126
        $beforeIndexItemsEvent = $this->eventDispatcher->dispatch($beforeIndexItemsEvent);
127
        $itemsToIndex = $beforeIndexItemsEvent->getItems();
128
129
        foreach ($itemsToIndex as $itemToIndex) {
130
            try {
131
                // try indexing
132
                $beforeIndexItemEvent = new BeforeIndexItemEvent($itemToIndex, $this->getContextTask(), $indexRunId);
133
                $beforeIndexItemEvent = $this->eventDispatcher->dispatch($beforeIndexItemEvent);
134
                $itemToIndex = $beforeIndexItemEvent->getItem();
135
                $this->indexItem($itemToIndex, $configurationToUse);
136
                $afterIndexItemEvent = new AfterIndexItemEvent($itemToIndex, $this->getContextTask(), $indexRunId);
137
                $this->eventDispatcher->dispatch($afterIndexItemEvent);
138
            } catch (Throwable $e) {
139
                $errors++;
140
                $this->indexQueue->markItemAsFailed($itemToIndex, $e->getCode() . ': ' . $e->__toString());
141
                $this->generateIndexingErrorLog($itemToIndex, $e);
142
            }
143
        }
144
145
        $afterIndexItemsEvent = new AfterIndexItemsEvent($itemsToIndex, $this->getContextTask(), $indexRunId);
146
        $this->eventDispatcher->dispatch($afterIndexItemsEvent);
147
148
        if ($enableCommitsSetting && count($itemsToIndex) > 0) {
149
            $solrServers = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionsBySite($this->site);
150
            foreach ($solrServers as $solrServer) {
151
                try {
152
                    $solrServer->getWriteService()->commit(false, false);
153
                } catch (HttpException $e) {
154
                    $errors++;
155
                }
156
            }
157
        }
158
159
        return $errors === 0;
160
    }
161
162
    /**
163
     * Generates a message in the error log when an error occurred.
164
     *
165
     * @param Item $itemToIndex
166
     * @param Throwable $e
167
     */
168
    protected function generateIndexingErrorLog(Item $itemToIndex, Throwable $e)
169
    {
170
        $message = 'Failed indexing Index Queue item ' . $itemToIndex->getIndexQueueUid();
171
        $data = ['code' => $e->getCode(), 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'item' => (array)$itemToIndex];
172
173
        $this->logger->log(
174
            SolrLogManager::ERROR,
175
            $message,
176
            $data
177
        );
178
    }
179
180
    /**
181
     * Indexes an item from the Index Queue.
182
     *
183
     * @param Item $item An index queue item to index
184
     * @param TypoScriptConfiguration $configuration
185
     * @return bool TRUE if the item was successfully indexed, FALSE otherwise
186
     * @throws Throwable
187
     */
188
    protected function indexItem(Item $item, TypoScriptConfiguration $configuration): bool
189
    {
190
        $indexer = $this->getIndexerByItem($item->getIndexingConfigurationName(), $configuration);
191
        // Remember original http host value
192
        $originalHttpHost = $_SERVER['HTTP_HOST'] ?? null;
193
194
        $itemChangedDate = $item->getChanged();
195
        $itemChangedDateAfterIndex = 0;
196
197
        try {
198
            $this->initializeHttpServerEnvironment($item);
199
            $itemIndexed = $indexer->index($item);
200
201
            // update IQ item so that the IQ can determine what's been indexed already
202
            if ($itemIndexed) {
203
                $this->indexQueue->updateIndexTimeByItem($item);
204
                $itemChangedDateAfterIndex = $item->getChanged();
205
            }
206
207
            if ($itemChangedDateAfterIndex > $itemChangedDate && $itemChangedDateAfterIndex > time()) {
208
                $this->indexQueue->setForcedChangeTimeByItem($item, $itemChangedDateAfterIndex);
0 ignored issues
show
Bug introduced by
It seems like $itemChangedDateAfterIndex can also be of type null; however, parameter $forcedChangeTime of ApacheSolrForTypo3\Solr\...orcedChangeTimeByItem() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

208
                $this->indexQueue->setForcedChangeTimeByItem($item, /** @scrutinizer ignore-type */ $itemChangedDateAfterIndex);
Loading history...
209
            }
210
        } catch (Throwable $e) {
211
            $this->restoreOriginalHttpHost($originalHttpHost);
212
            throw $e;
213
        }
214
215
        $this->restoreOriginalHttpHost($originalHttpHost);
216
217
        return $itemIndexed;
218
    }
219
220
    /**
221
     * A factory method to get an indexer depending on an item's configuration.
222
     *
223
     * By default, all items are indexed using the default indexer
224
     * (ApacheSolrForTypo3\Solr\IndexQueue\Indexer) coming with EXT:solr. Pages by default are
225
     * configured to be indexed through a dedicated indexer
226
     * (ApacheSolrForTypo3\Solr\IndexQueue\PageIndexer). In all other cases a dedicated indexer
227
     * can be specified through TypoScript if needed.
228
     *
229
     * @param string $indexingConfigurationName Indexing configuration name.
230
     * @param TypoScriptConfiguration $configuration
231
     * @return Indexer
232
     */
233
    protected function getIndexerByItem(
234
        string $indexingConfigurationName,
235
        TypoScriptConfiguration $configuration
236
    ): Indexer {
237
        $indexerClass = $configuration->getIndexQueueIndexerByConfigurationName($indexingConfigurationName);
238
        $indexerConfiguration = $configuration->getIndexQueueIndexerConfigurationByConfigurationName($indexingConfigurationName);
239
240
        $indexer = GeneralUtility::makeInstance($indexerClass, /** @scrutinizer ignore-type */ $indexerConfiguration);
241
        if (!($indexer instanceof Indexer)) {
242
            throw new RuntimeException(
243
                'The indexer class "' . $indexerClass . '" for indexing configuration "' . $indexingConfigurationName . '" is not a valid indexer. Must be a subclass of ApacheSolrForTypo3\Solr\IndexQueue\Indexer.',
244
                1260463206
245
            );
246
        }
247
248
        return $indexer;
249
    }
250
251
    /**
252
     * Gets the indexing progress.
253
     *
254
     * @return float Indexing progress as a two decimal precision float. f.e. 44.87
255
     */
256
    public function getProgress(): float
257
    {
258
        return $this->indexQueue->getStatisticsBySite($this->site)->getSuccessPercentage();
259
    }
260
261
    /**
262
     * Returns the amount of failed queue items for the current site.
263
     *
264
     * @return int
265
     */
266
    public function getFailCount(): int
267
    {
268
        return $this->indexQueue->getStatisticsBySite($this->site)->getFailedCount();
269
    }
270
271
    /**
272
     * Initializes the $_SERVER['HTTP_HOST'] environment variable in CLI
273
     * environments dependent on the Index Queue item's root page.
274
     *
275
     * When the Index Queue Worker task is executed by a cron job there is no
276
     * HTTP_HOST since we are in a CLI environment. RealURL needs the host
277
     * information to generate a proper URL though. Using the Index Queue item's
278
     * root page information we can determine the correct host although being
279
     * in a CLI environment.
280
     *
281
     * @param Item $item Index Queue item to use to determine the host.
282
     * @param
283
     */
284
    protected function initializeHttpServerEnvironment(Item $item)
285
    {
286
        static $hosts = [];
287
        $rootPageId = $item->getRootPageUid();
288
        $hostFound = !empty($hosts[$rootPageId]);
289
290
        if (!$hostFound) {
291
            $hosts[$rootPageId] = $item->getSite()->getDomain();
292
        }
293
294
        $_SERVER['HTTP_HOST'] = $hosts[$rootPageId];
295
296
        // needed since TYPO3 7.5
297
        GeneralUtility::flushInternalRuntimeCaches();
298
    }
299
300
    /**
301
     * @param string|null $originalHttpHost
302
     */
303
    protected function restoreOriginalHttpHost(?string $originalHttpHost)
304
    {
305
        if (!is_null($originalHttpHost)) {
306
            $_SERVER['HTTP_HOST'] = $originalHttpHost;
307
        } else {
308
            unset($_SERVER['HTTP_HOST']);
309
        }
310
311
        // needed since TYPO3 7.5
312
        GeneralUtility::flushInternalRuntimeCaches();
313
    }
314
}
315