[TASK] Doctrine: Migrate indexed_search part 1
[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\Database\DatabaseConnection;
21 use TYPO3\CMS\Core\Imaging\Icon;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
24 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
25 use TYPO3\CMS\Extbase\Mvc\Web\Request as WebRequest;
26 use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
27 use TYPO3\CMS\IndexedSearch\Domain\Repository\AdministrationRepository;
28 use TYPO3\CMS\IndexedSearch\Indexer;
29 use TYPO3\CMS\Lang\LanguageService;
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 = array();
50
51 /**
52 * @var array Configuration defined in the Extension Manager
53 */
54 protected $indexerConfig = array();
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 }
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(array(
218 'records' => $this->administrationRepository->getRecordsNumbers(),
219 'phash' => $this->administrationRepository->getPageHashTypes()
220 ));
221
222 if ($this->pageUid) {
223 $last24hours = ' AND tstamp > ' . ($GLOBALS['EXEC_TIME'] - 24 * 60 * 60);
224 $last30days = ' AND tstamp > ' . ($GLOBALS['EXEC_TIME'] - 30 * 24 * 60 * 60);
225
226 $this->view->assignMultiple(array(
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 * @return void
239 */
240 public function pagesAction()
241 {
242 $this->view->assign('records', $this->administrationRepository->getPageStatistic());
243 }
244
245 /**
246 * Statistics for external documents
247 *
248 * @return void
249 */
250 public function externalDocumentsAction()
251 {
252 $this->view->assign('records', $this->administrationRepository->getExternalDocumentsStatistic());
253 }
254
255 /**
256 * Statistics for a given page hash
257 *
258 * @param int $pageHash
259 * @return void
260 */
261 public function statisticDetailsAction($pageHash = 0)
262 {
263 // Set back button
264 $icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-up', Icon::SIZE_SMALL);
265 $backButton = $this->view->getModuleTemplate()->getDocHeaderComponent()
266 ->getButtonBar()->makeLinkButton()
267 ->setTitle($this->getLanguageService()->sL('LLL:EXT:indexed_search/Resources/Private/Language/locallang.xml:administration.back'))
268 ->setIcon($icon)
269 ->setHref($this->getHref('Administration', 'statistic'));
270 $this->view->getModuleTemplate()->getDocHeaderComponent()
271 ->getButtonBar()->addButton($backButton);
272
273 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_phash');
274 $pageHashRow = $queryBuilder
275 ->select('*')
276 ->from('index_phash')
277 ->where($queryBuilder->expr()->eq('phash', (int)$pageHash))
278 ->execute()
279 ->fetch();
280
281 if (!is_array($pageHashRow)) {
282 $this->redirect('statistic');
283 }
284
285 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_debug');
286 $debugRow = $queryBuilder
287 ->select('*')
288 ->from('index_debug')
289 ->where($queryBuilder->expr()->eq('phash', (int)$pageHash))
290 ->execute()
291 ->fetchAll();
292 $debugInfo = array();
293 $lexer = '';
294 if (is_array($debugRow)) {
295 $debugInfo = unserialize($debugRow[0]['debuginfo']);
296 $lexer = $debugInfo['lexer'];
297 unset($debugInfo['lexer']);
298 }
299 $pageRecord = BackendUtility::getRecord('pages', $pageHashRow['data_page_id']);
300 $keywords = is_array($pageRecord) ? array_flip(GeneralUtility::trimExplode(',', $pageRecord['keywords'], true)) : array();
301 $wordRecords = $db->exec_SELECTgetRows(
302 'index_words.*, index_rel.*',
303 'index_rel, index_words',
304 'index_rel.phash = ' . (int)$pageHash . ' AND index_words.wid = index_rel.wid',
305 '',
306 'index_words.baseword'
307 );
308 foreach ($wordRecords as $id => $row) {
309 if (isset($keywords[$row['baseword']])) {
310 $wordRecords[$id]['is_keyword'] = true;
311 }
312 }
313 $metaphoneRows = $metaphone = array();
314 if ($this->enableMetaphoneSearch && is_array($wordRecords)) {
315 // Group metaphone hash
316 foreach ($wordRecords as $row) {
317 $metaphoneRows[$row['metaphone']][] = $row['baseword'];
318 }
319
320 foreach ($metaphoneRows as $hash => $words) {
321 if (count($words) > 1) {
322 $metaphone[] = array(
323 'metaphone' => $this->indexer->metaphone($words[0], 1), $hash,
324 'words' => $words,
325 'hash' => $hash
326 );
327 }
328 }
329 }
330 $this->view->assignMultiple(array(
331 'phash' => (int)$pageHash,
332 'phashRow' => $pageHashRow,
333 'words' => $wordRecords,
334 'sections' => $db->exec_SELECTgetRows(
335 '*',
336 'index_section',
337 'index_section.phash = ' . (int)$pageHash
338 ),
339 'topCount' => $db->exec_SELECTgetRows(
340 'index_words.baseword, index_words.metaphone, index_rel.*',
341 'index_rel, index_words',
342 'index_rel.phash = ' . (int)$pageHash . ' AND index_words.wid = index_rel.wid
343 AND index_words.is_stopword=0',
344 '',
345 'index_rel.count DESC',
346 '20'
347 ),
348 'topFrequency' => $db->exec_SELECTgetRows(
349 'index_words.baseword, index_words.metaphone, index_rel.*',
350 'index_rel, index_words',
351 'index_rel.phash = ' . (int)$pageHash . ' AND index_words.wid = index_rel.wid
352 AND index_words.is_stopword=0',
353 '',
354 'index_rel.freq DESC',
355 '20'
356 ),
357 'debug' => $debugInfo,
358 'lexer' => $lexer,
359 'metaphone' => $metaphone,
360 'page' => $pageRecord,
361 'keywords' => $keywords
362 ));
363 }
364
365 /**
366 * Save stop words and keywords
367 *
368 * @param string $pageHash
369 * @param int $pageId
370 * @param array $stopwords
371 * @param array $keywords
372 * @return void
373 */
374 public function saveStopwordsKeywordsAction($pageHash, $pageId, $stopwords = array(), $keywords = array())
375 {
376 if ($this->getBackendUserAuthentication()->isAdmin()) {
377 if (is_array($stopwords) && !empty($stopwords)) {
378 $this->administrationRepository->saveStopWords($stopwords);
379 }
380 if (is_array($keywords) && !empty($keywords)) {
381 $this->administrationRepository->saveKeywords($keywords, $pageId);
382 }
383 }
384
385 $this->redirect('statisticDetails', null, null, array('pageHash' => $pageHash));
386 }
387
388 /**
389 * Statistics for a given word id
390 *
391 * @param int $id
392 * @param int $pageHash
393 * @return void
394 */
395 public function wordDetailAction($id = 0, $pageHash = 0)
396 {
397 $rows = $this->getDatabaseConnection()->exec_SELECTgetRows(
398 'index_phash.*, index_section.*, index_rel.*',
399 'index_rel, index_section, index_phash',
400 'index_rel.wid = ' . (int)$id . ' AND index_rel.phash = index_section.phash' . ' AND index_section.phash = index_phash.phash',
401 '',
402 'index_rel.freq DESC'
403 );
404
405 $this->view->assignMultiple(array(
406 'rows' => $rows,
407 'phash' => $pageHash
408 ));
409 }
410
411 /**
412 * General statistics
413 *
414 * @param int $depth
415 * @param string $mode
416 * @return void
417 */
418 public function statisticAction($depth = 1, $mode = 'overview')
419 {
420 if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['external_parsers'])) {
421 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef) {
422 /** @var \TYPO3\CMS\IndexedSearch\FileContentParser $fileContentParser */
423 $fileContentParser = GeneralUtility::getUserObj($_objRef);
424 if ($fileContentParser->softInit($extension)) {
425 $this->external_parsers[$extension] = $fileContentParser;
426 }
427 }
428 }
429 $this->administrationRepository->external_parsers = $this->external_parsers;
430
431 $allLines = $this->administrationRepository->getTree($this->pageUid, $depth, $mode);
432
433 $this->view->assignMultiple(array(
434 'levelTranslations' => explode('|', $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.enterSearchLevels')),
435 'tree' => $allLines,
436 'pageUid' => $this->pageUid,
437 'mode' => $mode,
438 'depth' => $depth
439 ));
440 }
441
442 /**
443 * Remove item from index
444 *
445 * @param string $id
446 * @param int $depth
447 * @param string $mode
448 * @return void
449 */
450 public function deleteIndexedItemAction($id, $depth = 1, $mode = 'overview')
451 {
452 $this->administrationRepository->removeIndexedPhashRow($id, $this->pageUid, $depth);
453 $this->redirect('statistic', null, null, array('depth' => $depth, 'mode' => $mode));
454 }
455
456 /**
457 * Creates te URI for a backend action
458 *
459 * @param string $controller
460 * @param string $action
461 * @param array $parameters
462 *
463 * @return string
464 */
465 protected function getHref($controller, $action, $parameters = [])
466 {
467 $uriBuilder = $this->objectManager->get(UriBuilder::class);
468 $uriBuilder->setRequest($this->request);
469 return $uriBuilder->reset()->uriFor($action, $parameters, $controller);
470 }
471
472 /**
473 * @return DatabaseConnection
474 */
475 protected function getDatabaseConnection()
476 {
477 return $GLOBALS['TYPO3_DB'];
478 }
479
480 /**
481 * @return BackendUserAuthentication
482 */
483 protected function getBackendUserAuthentication()
484 {
485 return $GLOBALS['BE_USER'];
486 }
487
488 /**
489 * @return LanguageService
490 */
491 protected function getLanguageService()
492 {
493 return $GLOBALS['LANG'];
494 }
495 }