Passed
Push — task/2976_TYPO3.11_compatibili... ( 4d1a77...3b9190 )
by Rafael
03:39
created

AbstractInitializer::buildUserWhereClause()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
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 ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository;
34
use Doctrine\DBAL\DBALException;
35
use TYPO3\CMS\Backend\Utility\BackendUtility;
36
use TYPO3\CMS\Core\Database\ConnectionPool;
37
use TYPO3\CMS\Core\Messaging\FlashMessageService;
38
use TYPO3\CMS\Core\Utility\GeneralUtility;
39
40
/**
41
 * Abstract Index Queue initializer with implementation  of methods for common
42
 * needs during Index Queue initialization.
43
 *
44
 * @author Ingo Renner <[email protected]>
45
 */
46
abstract class AbstractInitializer implements IndexQueueInitializer
47
{
48
49
    /**
50
     * Site to initialize
51
     *
52
     * @var Site
53
     */
54
    protected $site;
55
56
    /**
57
     * The type of items this initializer is handling.
58
     *
59
     * @var string
60
     */
61
    protected $type;
62
63
    /**
64
     * Index Queue configuration.
65
     *
66
     * @var array
67
     */
68
    protected $indexingConfiguration;
69
70
    /**
71
     * Indexing configuration name.
72
     *
73
     * @var string
74
     */
75
    protected $indexingConfigurationName;
76
77
    /**
78
     * Flash message queue
79
     *
80
     * @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue
81
     */
82
    protected $flashMessageQueue;
83
84
    /**
85
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
86
     */
87
    protected $logger = null;
88
89
    /**
90
     * @var QueueItemRepository
91
     */
92
    protected $queueItemRepository;
93
94
    /**
95
     * @var PagesRepository
96
     */
97
    protected PagesRepository $pagesRepository;
98
99
    /**
100
     * Constructor, prepares the flash message queue
101
     * @param QueueItemRepository|null $queueItemRepository
102
     */
103 13
    public function __construct(QueueItemRepository $queueItemRepository = null, PagesRepository $pagesRepository = null)
104
    {
105 13
        $this->logger = GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
106 13
        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
107 13
        $this->flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier('solr.queue.initializer');
108 13
        $this->queueItemRepository = $queueItemRepository ?? GeneralUtility::makeInstance(QueueItemRepository::class);
109 13
        $this->pagesRepository = $pagesRepository ?? GeneralUtility::makeInstance(PagesRepository::class);
110 13
    }
111
112
    /**
113
     * Sets the site for the initializer.
114
     *
115
     * @param Site $site The site to initialize Index Queue items for.
116
     */
117 13
    public function setSite(Site $site)
118
    {
119 13
        $this->site = $site;
120 13
    }
121
122
    /**
123
     * Set the type (usually a Db table name) of items to initialize.
124
     *
125
     * @param string $type Type to initialize.
126
     */
127
    public function setType($type)
128
    {
129
        $this->type = $type;
130
    }
131
132
    /**
133
     * Sets the configuration for how to index a type of items.
134
     *
135
     * @param array $indexingConfiguration Indexing configuration from TypoScript
136
     */
137 12
    public function setIndexingConfiguration(array $indexingConfiguration)
138
    {
139 12
        $this->indexingConfiguration = $indexingConfiguration;
140 12
    }
141
142
    /**
143
     * Sets the name of the indexing configuration to initialize.
144
     *
145
     * @param string $indexingConfigurationName Indexing configuration name
146
     */
147 13
    public function setIndexingConfigurationName($indexingConfigurationName)
148
    {
149 13
        $this->indexingConfigurationName = (string)$indexingConfigurationName;
150 13
    }
151
152
    /**
153
     * Initializes Index Queue items for a certain site and indexing
154
     * configuration.
155
     *
156
     * @return bool TRUE if initialization was successful, FALSE on error.
157
     */
158 12
    public function initialize()
159
    {
160
        /** @var ConnectionPool $connectionPool */
161 12
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
162
163 12
        $fetchItemsQuery = $this->buildSelectStatement() . ', "" as errors '
164 12
            . 'FROM ' . $this->type . ' '
165 12
            . 'WHERE '
166 12
            . $this->buildPagesClause()
167 12
            . $this->buildTcaWhereClause()
168 12
            . $this->buildUserWhereClause();
169
170
        try {
171 12
            if ($connectionPool->getConnectionForTable($this->type)->getParams() === $connectionPool->getConnectionForTable('tx_solr_indexqueue_item')->getParams()) {
172
                // If both tables are in the same DB, send only one query to copy all datas from one table to the other
173 12
                $initializationQuery = 'INSERT INTO tx_solr_indexqueue_item (root, item_type, item_uid, indexing_configuration, indexing_priority, changed, errors) ' . $fetchItemsQuery;
174 12
                $logData = ['query' => $initializationQuery];
175 12
                $logData['rows'] = $this->queueItemRepository->initializeByNativeSQLStatement($initializationQuery);
176
            } else {
177
                // If tables are using distinct connections, start by fetching items matching criteria
178
                $logData = ['query' => $fetchItemsQuery];
179
                $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

179
                $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...
180
                $logData['rows'] = count($items);
181
182
                if (count($items)) {
183
                    // Add items to the queue (if any)
184
                    $logData['rows'] = $connectionPool
185
                        ->getConnectionForTable('tx_solr_indexqueue_item')
186 12
                        ->bulkInsert('tx_solr_indexqueue_item', $items, array_keys($items[0]));
187
                }
188
            }
189
        } catch (DBALException $DBALException) {
190
            $logData['error'] = $DBALException->getCode() . ': ' . $DBALException->getMessage();
191
        }
192
193 12
        $this->logInitialization($logData);
194 12
        return true;
195
    }
196
197
    /**
198
     * Builds the SELECT part of the Index Queue initialization query.
199
     *
200
     */
201 13
    protected function buildSelectStatement()
202
    {
203 13
        $changedField = $GLOBALS['TCA'][$this->type]['ctrl']['tstamp'];
204 13
        if (!empty($GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['starttime'])) {
205 13
            $changedField = 'GREATEST(' . $GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['starttime'] . ',' . $GLOBALS['TCA'][$this->type]['ctrl']['tstamp'] . ')';
206
        }
207 13
        $select = 'SELECT '
208 13
            . '\'' . $this->site->getRootPageId() . '\' as root, '
209 13
            . '\'' . $this->type . '\' AS item_type, '
210 13
            . 'uid AS item_uid, '
211 13
            . '\'' . $this->indexingConfigurationName . '\' as indexing_configuration, '
212 13
            . $this->getIndexingPriority() . ' AS indexing_priority, '
213 13
            . $changedField . ' AS changed';
214
215 13
        return $select;
216
    }
217
218
    // initialization query building
219
220
    /**
221
     * Reads the indexing priority for an indexing configuration.
222
     *
223
     * @return int Indexing priority
224
     */
225 13
    protected function getIndexingPriority()
226
    {
227 13
        $priority = 0;
228
229 13
        if (!empty($this->indexingConfiguration['indexingPriority'])) {
230
            $priority = (int)$this->indexingConfiguration['indexingPriority'];
231
        }
232
233 13
        return $priority;
234
    }
235
236
    /**
237
     * Builds a part of the WHERE clause of the Index Queue initialization
238
     * query. This part selects the limits items to be selected from the pages
239
     * in a site only, plus additional pages that may have been configured.
240
     *
241
     */
242 12
    protected function buildPagesClause()
243
    {
244 12
        $pages = $this->getPages();
245 12
        $pageIdField = ($this->type === 'pages') ? 'uid' : 'pid';
246
247 12
        return $pageIdField . ' IN(' . implode(',', $pages) . ')';
248
    }
249
250
    /**
251
     * Gets the pages in a site plus additional pages that may have been
252
     * configured.
253
     *
254
     * @return array A (sorted) array of page IDs in a site
255
     */
256 12
    protected function getPages(): array
257
    {
258 12
        $pages = $this->site->getPages(null, $this->indexingConfigurationName);
259 12
        $additionalPageIds = [];
260 12
        if (!empty($this->indexingConfiguration['additionalPageIds'])) {
261
            $additionalPageIds = GeneralUtility::intExplode(',', $this->indexingConfiguration['additionalPageIds']);
262
        }
263
264 12
        $pages = array_merge($pages, $additionalPageIds);
265 12
        sort($pages, SORT_NUMERIC);
266
267 12
        $pagesWithinNoSearchSubEntriesPages = $this->pagesRepository->findAllPagesWithinNoSearchSubEntriesMarkedPages();
268
        // @todo: log properly if $additionalPageIds are within $pagesWithinNoSearchSubEntriesPages
269 12
        return array_values(array_diff($pages, $pagesWithinNoSearchSubEntriesPages));
270
    }
271
272
    /**
273
     * Builds the WHERE clauses of the Index Queue initialization query based
274
     * on TCA information for the type to be initialized.
275
     *
276
     * @return string Conditions to only add indexable items to the Index Queue
277
     */
278 13
    protected function buildTcaWhereClause()
279
    {
280 13
        $tcaWhereClause = '';
281 13
        $conditions = [];
282
283 13
        if (isset($GLOBALS['TCA'][$this->type]['ctrl']['delete'])) {
284 13
            $conditions['delete'] = $GLOBALS['TCA'][$this->type]['ctrl']['delete'] . ' = 0';
285
        }
286
287 13
        if (isset($GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['disabled'])) {
288 13
            $conditions['disabled'] = $GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['disabled'] . ' = 0';
289
        }
290
291 13
        if (isset($GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['endtime'])) {
292
            // only include records with a future endtime or default value (0)
293 13
            $endTimeFieldName = $GLOBALS['TCA'][$this->type]['ctrl']['enablecolumns']['endtime'];
294 13
            $conditions['endtime'] = '(' . $endTimeFieldName . ' > ' . time() . ' OR ' . $endTimeFieldName . ' = 0)';
295
        }
296
297 13
        if (BackendUtility::isTableLocalizable($this->type)) {
298 13
            $conditions['languageField'] = [
299
                // default language
300 13
                $GLOBALS['TCA'][$this->type]['ctrl']['languageField'] . ' = 0',
301
                // all languages
302 13
                $GLOBALS['TCA'][$this->type]['ctrl']['languageField'] . ' = -1'
303
            ];
304
            // all "free"-Mode languages for "non-pages"-records only
305 13
            if ($this->type !== 'pages' && $this->site->hasFreeContentModeLanguages()) {
306
                $conditions['languageField'][]
307
                    = $GLOBALS['TCA'][$this->type]['ctrl']['languageField']
308
                    . ' IN(/* free content mode */ '
309
                        . implode(',', $this->site->getFreeContentModeLanguages())
310
                    . ')';
311
            }
312
313 13
            if (isset($GLOBALS['TCA'][$this->type]['ctrl']['transOrigPointerField'])) {
314 13
                $conditions['languageField'][] = $GLOBALS['TCA'][$this->type]['ctrl']['transOrigPointerField'] . ' = 0'; // translations without original language source
315
            }
316 13
            $conditions['languageField'] = '(' . implode(' OR ',
317 13
                    $conditions['languageField']) . ')';
318
        }
319
320 13
        if (!empty($GLOBALS['TCA'][$this->type]['ctrl']['versioningWS'])) {
321
            // versioning is enabled for this table: exclude draft workspace records
322
            /* @see \TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction::buildExpression */
323 13
            $conditions['versioningWS'] = 't3ver_wsid = 0';
324
        }
325
326 13
        if (count($conditions)) {
327 13
            $tcaWhereClause = ' AND ' . implode(' AND ', $conditions);
328
        }
329
330 13
        return $tcaWhereClause;
331
    }
332
333
    /**
334
     * Builds the WHERE clauses of the Index Queue initialization query based
335
     * on TypoScript configuration for the type to be initialized.
336
     *
337
     * @return string Conditions to add items to the Index Queue based on TypoScript configuration
338
     */
339 13
    protected function buildUserWhereClause()
340
    {
341 13
        $condition = '';
342
343
        // FIXME replace this with the mechanism described below
344 13
        if (isset($this->indexingConfiguration['additionalWhereClause'])) {
345 12
            $condition = ' AND ' . $this->indexingConfiguration['additionalWhereClause'];
346
        }
347
348 13
        return $condition;
349
350
        // TODO add a query builder implementation based on TypoScript configuration
351
352
        /* example TypoScript
353
354
                @see http://docs.jboss.org/drools/release/5.4.0.Final/drools-expert-docs/html_single/index.html
355
                @see The Java Rule Engine API (JSR94)
356
357
                tt_news {
358
359
                        // RULES cObject provided by EXT:rules, simply evaluates to boolean TRUE or FALSE
360
                    conditions = RULES
361
                    conditions {
362
363
                        and {
364
365
                            10 {
366
                                field = pid
367
                                value = 2,3,5
368
                                condition = in / equals / notEquals / greaterThan / lessThan / greaterThanOrEqual / lessThanOrEqual
369
                            }
370
371
                            20 {
372
                                field = ...
373
                                value = ...
374
                                condition = ...
375
376
                                or {
377
                                    10 {
378
                                        field = ...
379
                                        value = ...
380
                                        condition =  ...
381
                                    }
382
383
                                    20 {
384
                                        field = ...
385
                                        value = ...
386
                                        condition = ...
387
                                    }
388
                                }
389
                            }
390
391
                        }
392
393
                    }
394
395
                    fields {
396
                        // field mapping
397
                    }
398
                }
399
        */
400
    }
401
402
    /**
403
     * Writes the passed log data to the log.
404
     *
405
     * @param array $logData
406
     */
407 13
    protected function logInitialization(array $logData)
408
    {
409 13
        if (!$this->site->getSolrConfiguration()->getLoggingIndexingIndexQueueInitialization()) {
410 13
            return;
411
        }
412
413
        $logSeverity = isset($logData['error']) ? SolrLogManager::ERROR : SolrLogManager::NOTICE;
414
        $logData = array_merge($logData, [
415
            'site' => $this->site->getLabel(),
416
            'indexing configuration name' => $this->indexingConfigurationName,
417
            'type' => $this->type,
418
        ]);
419
420
        $message = 'Index Queue initialized for indexing configuration ' . $this->indexingConfigurationName;
421
        $this->logger->log($logSeverity, $message, $logData);
422
    }
423
}
424