[TASK] Take care of rendering warnings in core Changelog rendering
[Packages/TYPO3.CMS.git] / typo3 / sysext / indexed_search / Classes / Controller / AdministrationController.php
1 <?php
2 namespace TYPO3\CMS\IndexedSearch\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16 use TYPO3\CMS\Backend\Utility\BackendUtility;
17 use TYPO3\CMS\Backend\View\BackendTemplateView;
18 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
19 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Imaging\Icon;
22 use TYPO3\CMS\Core\Localization\LanguageService;
23 use TYPO3\CMS\Core\Type\Bitmask\Permission;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
26 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
27 use TYPO3\CMS\Extbase\Mvc\Web\Request as WebRequest;
28 use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
29 use TYPO3\CMS\IndexedSearch\Domain\Repository\AdministrationRepository;
30 use TYPO3\CMS\IndexedSearch\Indexer;
31
32 /**
33 * Administration controller
34 * @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
35 */
36 class AdministrationController extends ActionController
37 {
38 /**
39 * @var AdministrationRepository
40 */
41 protected $administrationRepository;
42
43 /**
44 * @var int Current page id
45 */
46 protected $pageUid = 0;
47
48 /**
49 * @var array External parsers
50 */
51 protected $external_parsers = [];
52
53 /**
54 * @var array Configuration defined in the Extension Manager
55 */
56 protected $indexerConfig = [];
57
58 /**
59 * @var bool is metaphone enabled
60 */
61 protected $enableMetaphoneSearch = false;
62
63 /**
64 * Indexer object
65 *
66 * @var \TYPO3\CMS\IndexedSearch\Indexer
67 */
68 protected $indexer;
69
70 /**
71 * Backend Template Container
72 *
73 * @var BackendTemplateView
74 */
75 protected $defaultViewObjectName = BackendTemplateView::class;
76
77 /**
78 * BackendTemplateContainer
79 *
80 * @var BackendTemplateView
81 */
82 protected $view;
83
84 /**
85 * Set up the doc header properly here
86 *
87 * @param ViewInterface $view
88 */
89 protected function initializeView(ViewInterface $view)
90 {
91 if ($view instanceof BackendTemplateView) {
92 /** @var BackendTemplateView $view */
93 parent::initializeView($view);
94 $permissionClause = $this->getBackendUserAuthentication()->getPagePermsClause(Permission::PAGE_SHOW);
95 $pageRecord = BackendUtility::readPageAccess($this->pageUid, $permissionClause);
96 if ($pageRecord) {
97 $view->getModuleTemplate()->getDocHeaderComponent()->setMetaInformation($pageRecord);
98 }
99 $this->generateMenu();
100 $this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
101 $view->assign('extensionConfiguration', $this->indexerConfig);
102 }
103 }
104
105 /**
106 * Generates the action menu
107 */
108 protected function generateMenu()
109 {
110 $menuItems = [
111 'index' => [
112 'controller' => 'Administration',
113 'action' => 'index',
114 'label' => $this->getLanguageService()->sL('LLL:EXT:indexed_search/Resources/Private/Language/locallang.xlf:administration.menu.general')
115 ],
116 'pages' => [
117 'controller' => 'Administration',
118 'action' => 'pages',
119 'label' => $this->getLanguageService()->sL('LLL:EXT:indexed_search/Resources/Private/Language/locallang.xlf:administration.menu.pages')
120 ],
121 'externalDocuments' => [
122 'controller' => 'Administration',
123 'action' => 'externalDocuments',
124 'label' => $this->getLanguageService()->sL('LLL:EXT:indexed_search/Resources/Private/Language/locallang.xlf:administration.menu.externalDocuments')
125 ],
126 'statistic' => [
127 'controller' => 'Administration',
128 'action' => 'statistic',
129 'label' => $this->getLanguageService()->sL('LLL:EXT:indexed_search/Resources/Private/Language/locallang.xlf:administration.menu.statistic')
130 ]
131 ];
132 $uriBuilder = $this->objectManager->get(UriBuilder::class);
133 $uriBuilder->setRequest($this->request);
134
135 $menu = $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
136 $menu->setIdentifier('IndexedSearchModuleMenu');
137
138 foreach ($menuItems as $menuItemConfig) {
139 $isActive = $this->request->getControllerActionName() === $menuItemConfig['action'];
140 $menuItem = $menu->makeMenuItem()
141 ->setTitle($menuItemConfig['label'])
142 ->setHref($this->getHref($menuItemConfig['controller'], $menuItemConfig['action']))
143 ->setActive($isActive);
144 $menu->addMenuItem($menuItem);
145 }
146
147 $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
148 }
149
150 /**
151 * Function will be called before every other action
152 */
153 public function initializeAction()
154 {
155 $this->pageUid = (int)GeneralUtility::_GET('id');
156 $this->indexerConfig = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('indexed_search');
157 $this->enableMetaphoneSearch = (bool)$this->indexerConfig['enableMetaphoneSearch'];
158 $this->indexer = GeneralUtility::makeInstance(Indexer::class);
159
160 parent::initializeAction();
161 }
162
163 /**
164 * Override the action name if found in the uc of the user
165 *
166 * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request
167 * @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response
168 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException
169 */
170 public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response)
171 {
172 $vars = GeneralUtility::_GET('tx_indexedsearch_web_indexedsearchisearch');
173
174 $beUser = $this->getBackendUserAuthentication();
175 if (is_array($vars) && isset($vars['action']) && method_exists($this, $vars['action'] . 'Action')) {
176 $action = $vars['action'];
177
178 switch ($action) {
179 case 'saveStopwordsKeywords':
180 $action = 'statisticDetails';
181 break;
182 case 'deleteIndexedItem':
183 $action = 'statistic';
184 break;
185 }
186
187 $beUser->uc['indexed_search']['action'] = $action;
188 $beUser->uc['indexed_search']['arguments'] = $request->getArguments();
189 $beUser->writeUC();
190 } elseif (isset($beUser->uc['indexed_search']['action'])) {
191 if ($request instanceof WebRequest) {
192 $request->setControllerActionName($beUser->uc['indexed_search']['action']);
193 }
194 if (isset($beUser->uc['indexed_search']['arguments'])) {
195 $request->setArguments($beUser->uc['indexed_search']['arguments']);
196 }
197 }
198
199 parent::processRequest($request, $response);
200 }
201
202 /**
203 * @param \TYPO3\CMS\IndexedSearch\Domain\Repository\AdministrationRepository $administrationRepository
204 */
205 public function injectAdministrationRepository(AdministrationRepository $administrationRepository)
206 {
207 $this->administrationRepository = $administrationRepository;
208 }
209
210 /**
211 * Index action contains the most important statistics
212 */
213 public function indexAction()
214 {
215 $this->view->assignMultiple([
216 'records' => $this->administrationRepository->getRecordsNumbers(),
217 'phash' => $this->administrationRepository->getPageHashTypes()
218 ]);
219
220 if ($this->pageUid) {
221 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
222 ->getQueryBuilderForTable('index_stat_word')
223 ->expr();
224
225 $last24hours = $expressionBuilder->gt('tstamp', $GLOBALS['EXEC_TIME'] - 86400);
226 $last30days = $expressionBuilder->gt('tstamp', $GLOBALS['EXEC_TIME'] - 30 * 86400);
227
228 $this->view->assignMultiple([
229 'pageUid' => $this->pageUid,
230 'all' => $this->administrationRepository->getGeneralSearchStatistic('', $this->pageUid),
231 'last24hours' => $this->administrationRepository->getGeneralSearchStatistic($last24hours, $this->pageUid),
232 'last30days' => $this->administrationRepository->getGeneralSearchStatistic($last30days, $this->pageUid),
233 ]);
234 }
235 }
236
237 /**
238 * Statistics for pages
239 */
240 public function pagesAction()
241 {
242 $this->view->assign('records', $this->administrationRepository->getPageStatistic());
243 }
244
245 /**
246 * Statistics for external documents
247 */
248 public function externalDocumentsAction()
249 {
250 $this->view->assign('records', $this->administrationRepository->getExternalDocumentsStatistic());
251 }
252
253 /**
254 * Statistics for a given page hash
255 *
256 * @param int $pageHash
257 */
258 public function statisticDetailsAction($pageHash = 0)
259 {
260 $pageHash = (int)$pageHash;
261 // Set back button
262 $icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-up', Icon::SIZE_SMALL);
263 $backButton = $this->view->getModuleTemplate()->getDocHeaderComponent()
264 ->getButtonBar()->makeLinkButton()
265 ->setTitle($this->getLanguageService()->sL('LLL:EXT:indexed_search/Resources/Private/Language/locallang.xlf:administration.back'))
266 ->setIcon($icon)
267 ->setHref($this->getHref('Administration', 'statistic'));
268 $this->view->getModuleTemplate()->getDocHeaderComponent()
269 ->getButtonBar()->addButton($backButton);
270
271 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_phash');
272 $pageHashRow = $queryBuilder
273 ->select('*')
274 ->from('index_phash')
275 ->where(
276 $queryBuilder->expr()->eq(
277 'phash',
278 $queryBuilder->createNamedParameter($pageHash, \PDO::PARAM_INT)
279 )
280 )
281 ->execute()
282 ->fetch();
283
284 if (!is_array($pageHashRow)) {
285 $this->redirect('statistic');
286 }
287
288 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_debug');
289 $debugRow = $queryBuilder
290 ->select('debuginfo')
291 ->from('index_debug')
292 ->where(
293 $queryBuilder->expr()->eq(
294 'phash',
295 $queryBuilder->createNamedParameter($pageHash, \PDO::PARAM_INT)
296 )
297 )
298 ->execute()
299 ->fetch();
300 $debugInfo = [];
301 $lexer = '';
302 if (is_array($debugRow)) {
303 $debugInfo = json_decode($debugRow['debuginfo'], true);
304 $lexer = $debugInfo['lexer'];
305 unset($debugInfo['lexer']);
306 }
307 $pageRecord = BackendUtility::getRecord('pages', $pageHashRow['data_page_id']);
308 $keywords = is_array($pageRecord) ? array_flip(GeneralUtility::trimExplode(',', $pageRecord['keywords'], true)) : [];
309
310 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_words');
311 $wordRecords = $queryBuilder
312 ->select('index_words.*', 'index_rel.*')
313 ->from('index_words')
314 ->from('index_rel')
315 ->where(
316 $queryBuilder->expr()->eq(
317 'index_rel.phash',
318 $queryBuilder->createNamedParameter($pageHash, \PDO::PARAM_INT)
319 ),
320 $queryBuilder->expr()->eq(
321 'index_words.wid',
322 $queryBuilder->quoteIdentifier('index_rel.wid')
323 )
324 )
325 ->orderBy('index_words.baseword')
326 ->execute()
327 ->fetchAll();
328 foreach ($wordRecords as $id => $row) {
329 if (isset($keywords[$row['baseword']])) {
330 $wordRecords[$id]['is_keyword'] = true;
331 }
332 }
333 $metaphoneRows = $metaphone = [];
334 if ($this->enableMetaphoneSearch && is_array($wordRecords)) {
335 // Group metaphone hash
336 foreach ($wordRecords as $row) {
337 $metaphoneRows[$row['metaphone']][] = $row['baseword'];
338 }
339
340 foreach ($metaphoneRows as $hash => $words) {
341 if (count($words) > 1) {
342 $metaphone[] = [
343 'metaphone' => $this->indexer->metaphone($words[0], 1), $hash,
344 'words' => $words,
345 'hash' => $hash
346 ];
347 }
348 }
349 }
350
351 // sections
352 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_section');
353 $sections = $queryBuilder
354 ->select('*')
355 ->from('index_section')
356 ->where(
357 $queryBuilder->expr()->eq(
358 'phash',
359 $queryBuilder->createNamedParameter($pageHash, \PDO::PARAM_INT)
360 )
361 )
362 ->execute()
363 ->fetchAll();
364
365 // top words
366 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_words');
367 $topCountWords = $queryBuilder
368 ->select('index_words.baseword', 'index_words.metaphone', 'index_rel.*')
369 ->from('index_words')
370 ->from('index_rel')
371 ->setMaxResults(20)
372 ->where(
373 $queryBuilder->expr()->eq(
374 'index_rel.phash',
375 $queryBuilder->createNamedParameter($pageHash, \PDO::PARAM_INT)
376 ),
377 $queryBuilder->expr()->eq(
378 'index_words.is_stopword',
379 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
380 ),
381 $queryBuilder->expr()->eq(
382 'index_words.wid',
383 $queryBuilder->quoteIdentifier('index_rel.wid')
384 )
385 )
386 ->orderBy('index_rel.count', 'DESC')
387 ->execute()
388 ->fetchAll();
389
390 // top frequency
391 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_words');
392 $topFrequency = $queryBuilder
393 ->select('index_words.baseword', 'index_words.metaphone', 'index_rel.*')
394 ->from('index_words')
395 ->from('index_rel')
396 ->setMaxResults(20)
397 ->where(
398 $queryBuilder->expr()->eq(
399 'index_rel.phash',
400 $queryBuilder->createNamedParameter($pageHash, \PDO::PARAM_INT)
401 ),
402 $queryBuilder->expr()->eq(
403 'index_words.is_stopword',
404 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
405 ),
406 $queryBuilder->expr()->eq(
407 'index_words.wid',
408 $queryBuilder->quoteIdentifier('index_rel.wid')
409 )
410 )
411 ->orderBy('index_rel.freq', 'DESC')
412 ->execute()
413 ->fetchAll();
414
415 $this->view->assignMultiple([
416 'phash' => (int)$pageHash,
417 'phashRow' => $pageHashRow,
418 'words' => $wordRecords,
419 'sections' => $sections,
420 'topCount' => $topCountWords,
421 'topFrequency' => $topFrequency,
422 'debug' => $debugInfo,
423 'lexer' => $lexer,
424 'metaphone' => $metaphone,
425 'page' => $pageRecord,
426 'keywords' => $keywords
427 ]);
428 }
429
430 /**
431 * Save stop words and keywords
432 *
433 * @param string $pageHash
434 * @param int $pageId
435 * @param array $stopwords
436 * @param array $keywords
437 */
438 public function saveStopwordsKeywordsAction($pageHash, $pageId, $stopwords = [], $keywords = [])
439 {
440 if ($this->getBackendUserAuthentication()->isAdmin()) {
441 if (is_array($stopwords) && !empty($stopwords)) {
442 $this->administrationRepository->saveStopWords($stopwords);
443 }
444 if (is_array($keywords) && !empty($keywords)) {
445 $this->administrationRepository->saveKeywords($keywords, $pageId);
446 }
447 }
448
449 $this->redirect('statisticDetails', null, null, ['pageHash' => $pageHash]);
450 }
451
452 /**
453 * Statistics for a given word id
454 *
455 * @param int $id
456 * @param int $pageHash
457 */
458 public function wordDetailAction($id = 0, $pageHash = 0)
459 {
460 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_phash');
461 $rows = $queryBuilder
462 ->select('index_phash.*', 'index_section.*', 'index_rel.*')
463 ->from('index_rel')
464 ->from('index_section')
465 ->from('index_phash')
466 ->where(
467 $queryBuilder->expr()->eq(
468 'index_rel.wid',
469 $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
470 ),
471 $queryBuilder->expr()->eq(
472 'index_rel.phash',
473 $queryBuilder->quoteIdentifier('index_section.phash')
474 ),
475 $queryBuilder->expr()->eq(
476 'index_section.phash',
477 $queryBuilder->quoteIdentifier('index_phash.phash')
478 )
479 )
480 ->orderBy('index_rel.freq', 'desc')
481 ->execute()
482 ->fetchAll();
483
484 $this->view->assignMultiple([
485 'rows' => $rows,
486 'phash' => $pageHash
487 ]);
488 }
489
490 /**
491 * General statistics
492 *
493 * @param int $depth
494 * @param string $mode
495 */
496 public function statisticAction($depth = 1, $mode = 'overview')
497 {
498 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['external_parsers'] ?? [] as $extension => $className) {
499 /** @var \TYPO3\CMS\IndexedSearch\FileContentParser $fileContentParser */
500 $fileContentParser = GeneralUtility::makeInstance($className);
501 if ($fileContentParser->softInit($extension)) {
502 $this->external_parsers[$extension] = $fileContentParser;
503 }
504 }
505 $this->administrationRepository->external_parsers = $this->external_parsers;
506
507 $allLines = $this->administrationRepository->getTree($this->pageUid, $depth, $mode);
508
509 $this->view->assignMultiple([
510 'levelTranslations' => explode('|', $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enterSearchLevels')),
511 'tree' => $allLines,
512 'pageUid' => $this->pageUid,
513 'mode' => $mode,
514 'depth' => $depth
515 ]);
516 }
517
518 /**
519 * Remove item from index
520 *
521 * @param string $id
522 * @param int $depth
523 * @param string $mode
524 */
525 public function deleteIndexedItemAction($id, $depth = 1, $mode = 'overview')
526 {
527 $this->administrationRepository->removeIndexedPhashRow($id, $this->pageUid, $depth);
528 $this->redirect('statistic', null, null, ['depth' => $depth, 'mode' => $mode]);
529 }
530
531 /**
532 * Creates te URI for a backend action
533 *
534 * @param string $controller
535 * @param string $action
536 * @param array $parameters
537 *
538 * @return string
539 */
540 protected function getHref($controller, $action, $parameters = [])
541 {
542 $uriBuilder = $this->objectManager->get(UriBuilder::class);
543 $uriBuilder->setRequest($this->request);
544 return $uriBuilder->reset()->uriFor($action, $parameters, $controller);
545 }
546
547 /**
548 * @return BackendUserAuthentication
549 */
550 protected function getBackendUserAuthentication()
551 {
552 return $GLOBALS['BE_USER'];
553 }
554
555 /**
556 * @return LanguageService
557 */
558 protected function getLanguageService()
559 {
560 return $GLOBALS['LANG'];
561 }
562 }