Completed
Branch master (a6ebf8)
by Timo
03:27
created

AbstractInitializer   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 358
Duplicated Lines 0 %

Test Coverage

Coverage 61.22%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 30
eloc 100
c 2
b 0
f 0
dl 0
loc 358
ccs 90
cts 147
cp 0.6122
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
A setType() 0 3 1
B buildTcaWhereClause() 0 43 8
A buildUserWhereClause() 0 10 2
A setSite() 0 3 1
A buildSelectStatement() 0 15 2
A getPages() 0 12 2
A getIndexingPriority() 0 9 2
A setIndexingConfiguration() 0 3 1
A logInitialization() 0 15 3
A setIndexingConfigurationName() 0 3 1
A __construct() 0 6 1
A buildPagesClause() 0 6 2
A initialize() 0 37 4
1
<?php
2
namespace ApacheSolrForTypo3\Solr\IndexQueue\Initializer;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2011-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
 *  A copy is found in the textfile GPL.txt and important notices to the license
19
 *  from the author is found in LICENSE.txt distributed with these scripts.
20
 *
21
 *
22
 *  This script is distributed in the hope that it will be useful,
23
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25
 *  GNU General Public License for more details.
26
 *
27
 *  This copyright notice MUST APPEAR in all copies of the script!
28
 ***************************************************************/
29
30
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\QueueItemRepository;
31
use ApacheSolrForTypo3\Solr\Domain\Site\Site;
32
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
33
use Doctrine\DBAL\DBALException;
34
use TYPO3\CMS\Backend\Utility\BackendUtility;
35
use TYPO3\CMS\Core\Database\ConnectionPool;
36
use TYPO3\CMS\Core\Messaging\FlashMessageService;
37
use TYPO3\CMS\Core\Utility\GeneralUtility;
38
39
/**
40
 * Abstract Index Queue initializer with implementation  of methods for common
41
 * needs during Index Queue initialization.
42
 *
43
 * @author Ingo Renner <[email protected]>
44
 */
45
abstract class AbstractInitializer implements IndexQueueInitializer
46
{
47
48
    /**
49
     * Site to initialize
50
     *
51
     * @var Site
52
     */
53
    protected $site;
54
55
    /**
56
     * The type of items this initializer is handling.
57
     *
58
     * @var string
59
     */
60
    protected $type;
61
62
    /**
63
     * Index Queue configuration.
64
     *
65
     * @var array
66
     */
67
    protected $indexingConfiguration;
68
69
    /**
70
     * Indexing configuration name.
71
     *
72
     * @var string
73
     */
74
    protected $indexingConfigurationName;
75
76
    /**
77
     * Flash message queue
78
     *
79
     * @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue
80
     */
81
    protected $flashMessageQueue;
82
83
    /**
84
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
85
     */
86
    protected $logger = null;
87
88
    /**
89
     * @var QueueItemRepository
90
     */
91
    protected $queueItemRepository;
92
93
    /**
94
     * Constructor, prepares the flash message queue
95
     * @param QueueItemRepository|null $queueItemRepository
96
     */
97 12
    public function __construct(QueueItemRepository $queueItemRepository = null)
98
    {
99 12
        $this->logger = GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
100 12
        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
101 12
        $this->flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier('solr.queue.initializer');
102 12
        $this->queueItemRepository = $queueItemRepository ?? GeneralUtility::makeInstance(QueueItemRepository::class);
103 12
    }
104
105
    /**
106
     * Sets the site for the initializer.
107
     *
108
     * @param Site $site The site to initialize Index Queue items for.
109
     */
110 12
    public function setSite(Site $site)
111
    {
112 12
        $this->site = $site;
113 12
    }
114
115
    /**
116
     * Set the type (usually a Db table name) of items to initialize.
117
     *
118
     * @param string $type Type to initialize.
119
     */
120 1
    public function setType($type)
121
    {
122 1
        $this->type = $type;
123 1
    }
124
125
    /**
126
     * Sets the configuration for how to index a type of items.
127
     *
128
     * @param array $indexingConfiguration Indexing configuration from TypoScript
129
     */
130 6
    public function setIndexingConfiguration(array $indexingConfiguration)
131
    {
132 6
        $this->indexingConfiguration = $indexingConfiguration;
133 6
    }
134
135
    /**
136
     * Sets the name of the indexing configuration to initialize.
137
     *
138
     * @param string $indexingConfigurationName Indexing configuration name
139
     */
140 11
    public function setIndexingConfigurationName($indexingConfigurationName)
141
    {
142 11
        $this->indexingConfigurationName = (string)$indexingConfigurationName;
143 11
    }
144
145
    /**
146
     * Initializes Index Queue items for a certain site and indexing
147
     * configuration.
148
     *
149
     * @return bool TRUE if initialization was successful, FALSE on error.
150
     */
151 11
    public function initialize()
152
    {
153
        /** @var ConnectionPool $connectionPool */
154 11
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
155
156 11
        $fetchItemsQuery = $this->buildSelectStatement() . ', "" as errors '
157 11
            . 'FROM ' . $this->type . ' '
158 11
            . 'WHERE '
159 11
            . $this->buildPagesClause()
160 11
            . $this->buildTcaWhereClause()
161 11
            . $this->buildUserWhereClause();
162
163
        try {
164 11
            if ($connectionPool->getConnectionForTable($this->type)->getParams() === $connectionPool->getConnectionForTable('tx_solr_indexqueue_item')->getParams()) {
165
                // If both tables are in the same DB, send only one query to copy all datas from one table to the other
166 11
                $initializationQuery = 'INSERT INTO tx_solr_indexqueue_item (root, item_type, item_uid, indexing_configuration, indexing_priority, changed, errors) ' . $fetchItemsQuery;
167 11
                $logData = ['query' => $initializationQuery];
168 11
                $logData['rows'] = $this->queueItemRepository->initializeByNativeSQLStatement($initializationQuery);
169
            } else {
170
                // If tables are using distinct connections, start by fetching items matching criteria
171
                $logData = ['query' => $fetchItemsQuery];
172
                $items = $connectionPool->getConnectionForTable($this->type)->fetchAll($fetchItemsQuery);
173
                $logData['rows'] = count($items);
174
175
                if (count($items)) {
176
                    // Add items to the queue (if any)
177
                    $logData['rows'] = $connectionPool
178
                        ->getConnectionForTable('tx_solr_indexqueue_item')
179 11
                        ->bulkInsert('tx_solr_indexqueue_item', $items, array_keys($items[0]));
180
                }
181
            }
182
        } catch (DBALException $DBALException) {
183
            $logData['error'] = $DBALException->getCode() . ': ' . $DBALException->getMessage();
184
        }
185
186 11
        $this->logInitialization($logData);
187 11
        return true;
188
    }
189
190
    /**
191
     * Builds the SELECT part of the Index Queue initialization query.
192
     *
193
     */
194 12
    protected function buildSelectStatement()
195
    {
196 12
        $changedField = $GLOBALS['TCA'][$this->type]['ctrl']['tstamp'];
197 12
        if (!empty($GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['starttime'])) {
198 12
            $changedField = 'GREATEST(' . $GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['starttime'] . ',' . $GLOBALS['TCA'][$this->type]['ctrl']['tstamp'] . ')';
199
        }
200
        $select = 'SELECT '
201 12
            . '\'' . $this->site->getRootPageId() . '\' as root, '
202 12
            . '\'' . $this->type . '\' AS item_type, '
203 12
            . 'uid AS item_uid, '
204 12
            . '\'' . $this->indexingConfigurationName . '\' as indexing_configuration, '
205 12
            . $this->getIndexingPriority() . ' AS indexing_priority, '
206 12
            . $changedField . ' AS changed';
207
208 12
        return $select;
209
    }
210
211
    // initialization query building
212
213
    /**
214
     * Reads the indexing priority for an indexing configuration.
215
     *
216
     * @return int Indexing priority
217
     */
218 12
    protected function getIndexingPriority()
219
    {
220 12
        $priority = 0;
221
222 12
        if (!empty($this->indexingConfiguration['indexingPriority'])) {
223
            $priority = (int)$this->indexingConfiguration['indexingPriority'];
224
        }
225
226 12
        return $priority;
227
    }
228
229
    /**
230
     * Builds a part of the WHERE clause of the Index Queue initialization
231
     * query. This part selects the limits items to be selected from the pages
232
     * in a site only, plus additional pages that may have been configured.
233
     *
234
     */
235 11
    protected function buildPagesClause()
236
    {
237 11
        $pages = $this->getPages();
238 11
        $pageIdField = ($this->type === 'pages') ? 'uid' : 'pid';
239
240 11
        return $pageIdField . ' IN(' . implode(',', $pages) . ')';
241
    }
242
243
    /**
244
     * Gets the pages in a site plus additional pages that may have been
245
     * configured.
246
     *
247
     * @return array A (sorted) array of page IDs in a site
248
     */
249 11
    protected function getPages()
250
    {
251 11
        $pages = $this->site->getPages();
252 11
        $additionalPageIds = [];
253 11
        if (!empty($this->indexingConfiguration['additionalPageIds'])) {
254
            $additionalPageIds = GeneralUtility::intExplode(',', $this->indexingConfiguration['additionalPageIds']);
255
        }
256
257 11
        $pages = array_merge($pages, $additionalPageIds);
258 11
        sort($pages, SORT_NUMERIC);
259
260 11
        return $pages;
261
    }
262
263
    /**
264
     * Builds the WHERE clauses of the Index Queue initialization query based
265
     * on TCA information for the type to be initialized.
266
     *
267
     * @return string Conditions to only add indexable items to the Index Queue
268
     */
269 12
    protected function buildTcaWhereClause()
270
    {
271 12
        $tcaWhereClause = '';
272 12
        $conditions = [];
273
274 12
        if (isset($GLOBALS['TCA'][$this->type]['ctrl']['delete'])) {
275 12
            $conditions['delete'] = $GLOBALS['TCA'][$this->type]['ctrl']['delete'] . ' = 0';
276
        }
277
278 12
        if (isset($GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['disabled'])) {
279 12
            $conditions['disabled'] = $GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['disabled'] . ' = 0';
280
        }
281
282 12
        if (isset($GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['endtime'])) {
283
            // only include records with a future endtime or default value (0)
284 12
            $endTimeFieldName = $GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['endtime'];
285 12
            $conditions['endtime'] = '(' . $endTimeFieldName . ' > ' . time() . ' OR ' . $endTimeFieldName . ' = 0)';
286
        }
287
288 12
        if (BackendUtility::isTableLocalizable($this->type)) {
289 12
            $conditions['languageField'] = [
290 12
                $GLOBALS['TCA'][$this->type]['ctrl']['languageField'] . ' = 0',
291
                // default language
292 12
                $GLOBALS['TCA'][$this->type]['ctrl']['languageField'] . ' = -1'
293
                // all languages
294
            ];
295 12
            if (isset($GLOBALS['TCA'][$this->type]['ctrl']['transOrigPointerField'])) {
296 12
                $conditions['languageField'][] = $GLOBALS['TCA'][$this->type]['ctrl']['transOrigPointerField'] . ' = 0'; // translations without original language source
297
            }
298 12
            $conditions['languageField'] = '(' . implode(' OR ',
299 12
                    $conditions['languageField']) . ')';
300
        }
301
302 12
        if (!empty($GLOBALS['TCA'][$this->type]['ctrl']['versioningWS'])) {
303
            // versioning is enabled for this table: exclude draft workspace records
304 12
            $conditions['versioningWS'] = 'pid != -1';
305
        }
306
307 12
        if (count($conditions)) {
308 12
            $tcaWhereClause = ' AND ' . implode(' AND ', $conditions);
309
        }
310
311 12
        return $tcaWhereClause;
312
    }
313
314
    /**
315
     * Builds the WHERE clauses of the Index Queue initialization query based
316
     * on TypoScript configuration for the type to be initialized.
317
     *
318
     * @return string Conditions to add items to the Index Queue based on TypoScript configuration
319
     */
320 12
    protected function buildUserWhereClause()
321
    {
322 12
        $condition = '';
323
324
        // FIXME replace this with the mechanism described below
325 12
        if (isset($this->indexingConfiguration['additionalWhereClause'])) {
326 5
            $condition = ' AND ' . $this->indexingConfiguration['additionalWhereClause'];
327
        }
328
329 12
        return $condition;
330
331
        // TODO add a query builder implementation based on TypoScript configuration
332
333
        /* example TypoScript
334
335
                @see http://docs.jboss.org/drools/release/5.4.0.Final/drools-expert-docs/html_single/index.html
336
                @see The Java Rule Engine API (JSR94)
337
338
                tt_news {
339
340
                        // RULES cObject provided by EXT:rules, simply evaluates to boolean TRUE or FALSE
341
                    conditions = RULES
342
                    conditions {
343
344
                        and {
345
346
                            10 {
347
                                field = pid
348
                                value = 2,3,5
349
                                condition = in / equals / notEquals / greaterThan / lessThan / greaterThanOrEqual / lessThanOrEqual
350
                            }
351
352
                            20 {
353
                                field = ...
354
                                value = ...
355
                                condition = ...
356
357
                                or {
358
                                    10 {
359
                                        field = ...
360
                                        value = ...
361
                                        condition =  ...
362
                                    }
363
364
                                    20 {
365
                                        field = ...
366
                                        value = ...
367
                                        condition = ...
368
                                    }
369
                                }
370
                            }
371
372
                        }
373
374
                    }
375
376
                    fields {
377
                        // field mapping
378
                    }
379
                }
380
        */
381
    }
382
383
    /**
384
     * Writes the passed log data to the log.
385
     *
386
     * @param array $logData
387
     */
388 12
    protected function logInitialization(array $logData)
389
    {
390 12
        if (!$this->site->getSolrConfiguration()->getLoggingIndexingIndexQueueInitialization()) {
391 12
            return;
392
        }
393
394
        $logSeverity = isset($logData['error']) ? SolrLogManager::ERROR : SolrLogManager::NOTICE;
395
        $logData = array_merge($logData, [
396
            'site' => $this->site->getLabel(),
397
            'indexing configuration name' => $this->indexingConfigurationName,
398
            'type' => $this->type,
399
        ]);
400
401
        $message = 'Index Queue initialized for indexing configuration ' . $this->indexingConfigurationName;
402
        $this->logger->log($logSeverity, $message, $logData);
403
    }
404
}
405