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