Completed
Push — master ( 909d34...d3109a )
by Timo
48:57 queued 45:09
created

IndexService::generateIndexingErrorLog()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
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 6
    public function __construct(Site $site, Queue $queue = null, Dispatcher $dispatcher = null, SolrLogManager $solrLogManager = null)
86
    {
87 6
        $this->site = $site;
88 6
        $this->indexQueue = $queue ?? GeneralUtility::makeInstance(Queue::class);
89 6
        $this->signalSlotDispatcher = $dispatcher ?? GeneralUtility::makeInstance(Dispatcher::class);
90 6
        $this->logger = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
91 6
        define('EXT_SOLR_INDEXING_CONTEXT', true);
92 6
    }
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 4
    public function getContextTask()
106
    {
107 4
        return $this->contextTask;
108
    }
109
110
    /**
111
     * Indexes items from the Index Queue.
112
     *
113
     * @param int $limit
114
     * @return bool
115
     */
116 4
    public function indexItems($limit)
117
    {
118 4
        $errors     = 0;
119 4
        $indexRunId = uniqid();
120 4
        $configurationToUse = $this->site->getSolrConfiguration();
121 4
        $enableCommitsSetting = $configurationToUse->getEnableCommits();
122
123
        // get items to index
124 4
        $itemsToIndex = $this->indexQueue->getItemsToIndex($this->site, $limit);
125
126 4
        $this->emitSignal('beforeIndexItems', [$itemsToIndex, $this->getContextTask(), $indexRunId]);
127
128 4
        foreach ($itemsToIndex as $itemToIndex) {
129
            try {
130
                // try indexing
131 4
                $this->emitSignal('beforeIndexItem', [$itemToIndex, $this->getContextTask(), $indexRunId]);
132 4
                $this->indexItem($itemToIndex, $configurationToUse);
133 3
                $this->emitSignal('afterIndexItem', [$itemToIndex, $this->getContextTask(), $indexRunId]);
134 1
            } catch (\Exception $e) {
135 1
                $errors++;
136 1
                $this->indexQueue->markItemAsFailed($itemToIndex, $e->getCode() . ': ' . $e->__toString());
137 1
                $this->generateIndexingErrorLog($itemToIndex, $e);
138
            }
139
        }
140
141 4
        $this->emitSignal('afterIndexItems', [$itemsToIndex, $this->getContextTask(), $indexRunId]);
142
143 4
        if ($enableCommitsSetting && count($itemsToIndex) > 0) {
144 1
            $solrServers = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionsBySite($this->site);
145 1
            foreach ($solrServers as $solrServer) {
146
                try {
147 1
                    $solrServer->getWriteService()->commit(false, false, false);
148
                } catch (HttpException $e) {
149
                    $errors++;
150
                }
151
            }
152
        }
153
154 4
        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 1
    protected function generateIndexingErrorLog(Item $itemToIndex, \Exception $e)
164
    {
165 1
        $message = 'Failed indexing Index Queue item ' . $itemToIndex->getIndexQueueUid();
166 1
        $data = ['code' => $e->getCode(), 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'item' => (array)$itemToIndex];
167
168 1
        $this->logger->log(
169 1
            SolrLogManager::ERROR,
170 1
            $message,
171 1
            $data
172
        );
173 1
    }
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 4
    protected function emitSignal($name, $arguments)
183
    {
184 4
        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 3
    protected function indexItem(Item $item, TypoScriptConfiguration $configuration)
195
    {
196 3
        $indexer = $this->getIndexerByItem($item->getIndexingConfigurationName(), $configuration);
197
198
        // Remember original http host value
199 3
        $originalHttpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
200
201 3
        $itemChangedDate = $item->getChanged();
202 3
        $itemChangedDateAfterIndex = 0;
203
204
        try {
205 3
            $this->initializeHttpServerEnvironment($item);
206 3
            $itemIndexed = $indexer->index($item);
207
208
            // update IQ item so that the IQ can determine what's been indexed already
209 2
            if ($itemIndexed) {
210 2
                $this->indexQueue->updateIndexTimeByItem($item);
211 2
                $itemChangedDateAfterIndex = $item->getChanged();
212
            }
213
214 2
            if ($itemChangedDateAfterIndex > $itemChangedDate && $itemChangedDateAfterIndex > time()) {
215 2
                $this->indexQueue->setForcedChangeTimeByItem($item, $itemChangedDateAfterIndex);
216
            }
217 1
        } catch (\Exception $e) {
218 1
            $this->restoreOriginalHttpHost($originalHttpHost);
219 1
            throw $e;
220
        }
221
222 2
        $this->restoreOriginalHttpHost($originalHttpHost);
223
224 2
        return $itemIndexed;
225
    }
226
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 1
    protected function getIndexerByItem($indexingConfigurationName, TypoScriptConfiguration $configuration)
241
    {
242 1
        $indexerClass = $configuration->getIndexQueueIndexerByConfigurationName($indexingConfigurationName);
243 1
        $indexerConfiguration = $configuration->getIndexQueueIndexerConfigurationByConfigurationName($indexingConfigurationName);
244
245 1
        $indexer = GeneralUtility::makeInstance($indexerClass, /** @scrutinizer ignore-type */ $indexerConfiguration);
246 1
        if (!($indexer instanceof Indexer)) {
247
            throw new \RuntimeException(
248
                '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 1
        return $indexer;
254
    }
255
256
    /**
257
     * Gets the indexing progress.
258
     *
259
     * @return float Indexing progress as a two decimal precision float. f.e. 44.87
260
     */
261 1
    public function getProgress()
262
    {
263 1
        return $this->indexQueue->getStatisticsBySite($this->site)->getSuccessPercentage();
264
    }
265
266
    /**
267
     * Returns the amount of failed queue items for the current site.
268
     *
269
     * @return int
270
     */
271 1
    public function getFailCount()
272
    {
273 1
        return $this->indexQueue->getStatisticsBySite($this->site)->getFailedCount();
274
    }
275
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 3
    protected function initializeHttpServerEnvironment(Item $item)
290
    {
291 3
        static $hosts = [];
292 3
        $rootpageId = $item->getRootPageUid();
293 3
        $hostFound = !empty($hosts[$rootpageId]);
294
295 3
        if (!$hostFound) {
296 3
            $hosts[$rootpageId] = $item->getSite()->getDomain();
297
        }
298
299 3
        $_SERVER['HTTP_HOST'] = $hosts[$rootpageId];
300
301
        // needed since TYPO3 7.5
302 3
        GeneralUtility::flushInternalRuntimeCaches();
303 3
    }
304
305
    /**
306
     * @param string|null $originalHttpHost
307
     */
308 2
    protected function restoreOriginalHttpHost($originalHttpHost)
309
    {
310 2
        if (!is_null($originalHttpHost)) {
311 1
            $_SERVER['HTTP_HOST'] = $originalHttpHost;
312
        } else {
313 1
            unset($_SERVER['HTTP_HOST']);
314
        }
315
316
        // needed since TYPO3 7.5
317 2
        GeneralUtility::flushInternalRuntimeCaches();
318 2
    }
319
}
320