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); |
|
|
|
|
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
|
|
|
|
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.