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