Passed
Push — master ( 4151a2...3c2751 )
by Rafael
136:43 queued 133:20
created

AbstractInitializer   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 362
Duplicated Lines 0 %

Test Coverage

Coverage 61.73%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 30
eloc 102
c 3
b 0
f 0
dl 0
loc 362
ccs 92
cts 149
cp 0.6173
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
A setType() 0 3 1
A setSite() 0 3 1
A buildSelectStatement() 0 15 2
A getIndexingPriority() 0 9 2
A setIndexingConfiguration() 0 3 1
A setIndexingConfigurationName() 0 3 1
A __construct() 0 6 1
A buildPagesClause() 0 6 2
A initialize() 0 37 4
B buildTcaWhereClause() 0 44 8
A buildUserWhereClause() 0 10 2
A getPages() 0 15 2
A logInitialization() 0 15 3
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);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::fetchAll() has been deprecated: Use fetchAllAssociative() ( Ignorable by Annotation )

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

172
                $items = /** @scrutinizer ignore-deprecated */ $connectionPool->getConnectionForTable($this->type)->fetchAll($fetchItemsQuery);

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...
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
        $pagesWithinNoSearchSubEntriesPages = $this->site->getPagesWithinNoSearchSubEntriesPages();
261
        // @todo: log properly if $additionalPageIds are within $pagesWithinNoSearchSubEntriesPages
262 11
        $pages = array_values(array_diff($pages, $pagesWithinNoSearchSubEntriesPages));
263 11
        return $pages;
264
    }
265
266
    /**
267
     * Builds the WHERE clauses of the Index Queue initialization query based
268
     * on TCA information for the type to be initialized.
269
     *
270
     * @return string Conditions to only add indexable items to the Index Queue
271
     */
272 12
    protected function buildTcaWhereClause()
273
    {
274 12
        $tcaWhereClause = '';
275 12
        $conditions = [];
276
277 12
        if (isset($GLOBALS['TCA'][$this->type]['ctrl']['delete'])) {
278 12
            $conditions['delete'] = $GLOBALS['TCA'][$this->type]['ctrl']['delete'] . ' = 0';
279
        }
280
281 12
        if (isset($GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['disabled'])) {
282 12
            $conditions['disabled'] = $GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['disabled'] . ' = 0';
283
        }
284
285 12
        if (isset($GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['endtime'])) {
286
            // only include records with a future endtime or default value (0)
287 12
            $endTimeFieldName = $GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['endtime'];
288 12
            $conditions['endtime'] = '(' . $endTimeFieldName . ' > ' . time() . ' OR ' . $endTimeFieldName . ' = 0)';
289
        }
290
291 12
        if (BackendUtility::isTableLocalizable($this->type)) {
292 12
            $conditions['languageField'] = [
293 12
                $GLOBALS['TCA'][$this->type]['ctrl']['languageField'] . ' = 0',
294
                // default language
295 12
                $GLOBALS['TCA'][$this->type]['ctrl']['languageField'] . ' = -1'
296
                // all languages
297
            ];
298 12
            if (isset($GLOBALS['TCA'][$this->type]['ctrl']['transOrigPointerField'])) {
299 12
                $conditions['languageField'][] = $GLOBALS['TCA'][$this->type]['ctrl']['transOrigPointerField'] . ' = 0'; // translations without original language source
300
            }
301 12
            $conditions['languageField'] = '(' . implode(' OR ',
302 12
                    $conditions['languageField']) . ')';
303
        }
304
305 12
        if (!empty($GLOBALS['TCA'][$this->type]['ctrl']['versioningWS'])) {
306
            // versioning is enabled for this table: exclude draft workspace records
307
            /* @see \TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction::buildExpression */
308 12
            $conditions['versioningWS'] = 't3ver_wsid = 0';
309
        }
310
311 12
        if (count($conditions)) {
312 12
            $tcaWhereClause = ' AND ' . implode(' AND ', $conditions);
313
        }
314
315 12
        return $tcaWhereClause;
316
    }
317
318
    /**
319
     * Builds the WHERE clauses of the Index Queue initialization query based
320
     * on TypoScript configuration for the type to be initialized.
321
     *
322
     * @return string Conditions to add items to the Index Queue based on TypoScript configuration
323
     */
324 12
    protected function buildUserWhereClause()
325
    {
326 12
        $condition = '';
327
328
        // FIXME replace this with the mechanism described below
329 12
        if (isset($this->indexingConfiguration['additionalWhereClause'])) {
330 5
            $condition = ' AND ' . $this->indexingConfiguration['additionalWhereClause'];
331
        }
332
333 12
        return $condition;
334
335
        // TODO add a query builder implementation based on TypoScript configuration
336
337
        /* example TypoScript
338
339
                @see http://docs.jboss.org/drools/release/5.4.0.Final/drools-expert-docs/html_single/index.html
340
                @see The Java Rule Engine API (JSR94)
341
342
                tt_news {
343
344
                        // RULES cObject provided by EXT:rules, simply evaluates to boolean TRUE or FALSE
345
                    conditions = RULES
346
                    conditions {
347
348
                        and {
349
350
                            10 {
351
                                field = pid
352
                                value = 2,3,5
353
                                condition = in / equals / notEquals / greaterThan / lessThan / greaterThanOrEqual / lessThanOrEqual
354
                            }
355
356
                            20 {
357
                                field = ...
358
                                value = ...
359
                                condition = ...
360
361
                                or {
362
                                    10 {
363
                                        field = ...
364
                                        value = ...
365
                                        condition =  ...
366
                                    }
367
368
                                    20 {
369
                                        field = ...
370
                                        value = ...
371
                                        condition = ...
372
                                    }
373
                                }
374
                            }
375
376
                        }
377
378
                    }
379
380
                    fields {
381
                        // field mapping
382
                    }
383
                }
384
        */
385
    }
386
387
    /**
388
     * Writes the passed log data to the log.
389
     *
390
     * @param array $logData
391
     */
392 12
    protected function logInitialization(array $logData)
393
    {
394 12
        if (!$this->site->getSolrConfiguration()->getLoggingIndexingIndexQueueInitialization()) {
395 12
            return;
396
        }
397
398
        $logSeverity = isset($logData['error']) ? SolrLogManager::ERROR : SolrLogManager::NOTICE;
399
        $logData = array_merge($logData, [
400
            'site' => $this->site->getLabel(),
401
            'indexing configuration name' => $this->indexingConfigurationName,
402
            'type' => $this->type,
403
        ]);
404
405
        $message = 'Index Queue initialized for indexing configuration ' . $this->indexingConfigurationName;
406
        $this->logger->log($logSeverity, $message, $logData);
407
    }
408
}
409