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