1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace AOE\Crawler\Backend\RequestForm; |
||
6 | |||
7 | use AOE\Crawler\Backend\Helper\ResultHandler; |
||
8 | use AOE\Crawler\Backend\Helper\UrlBuilder; |
||
9 | use AOE\Crawler\Converter\JsonCompatibilityConverter; |
||
10 | use AOE\Crawler\Domain\Repository\QueueRepository; |
||
11 | use AOE\Crawler\Utility\MessageUtility; |
||
12 | use AOE\Crawler\Value\QueueFilter; |
||
13 | use AOE\Crawler\Writer\FileWriter\CsvWriter\CrawlerCsvWriter; |
||
14 | use AOE\Crawler\Writer\FileWriter\CsvWriter\CsvWriterInterface; |
||
15 | use Doctrine\DBAL\Query\QueryBuilder; |
||
16 | use TYPO3\CMS\Backend\Tree\View\PageTreeView; |
||
17 | use TYPO3\CMS\Backend\Utility\BackendUtility; |
||
18 | use TYPO3\CMS\Core\Database\ConnectionPool; |
||
19 | use TYPO3\CMS\Core\Imaging\Icon; |
||
20 | use TYPO3\CMS\Core\Imaging\IconFactory; |
||
21 | use TYPO3\CMS\Core\Utility\DebugUtility; |
||
22 | use TYPO3\CMS\Core\Utility\GeneralUtility; |
||
23 | use TYPO3\CMS\Extbase\Object\ObjectManager; |
||
24 | use TYPO3\CMS\Fluid\View\StandaloneView; |
||
25 | use TYPO3\CMS\Info\Controller\InfoModuleController; |
||
26 | |||
27 | final class LogRequestForm extends AbstractRequestForm implements RequestFormInterface |
||
28 | { |
||
29 | /** |
||
30 | * @var StandaloneView |
||
31 | */ |
||
32 | private $view; |
||
33 | |||
34 | /** |
||
35 | * @var JsonCompatibilityConverter |
||
36 | */ |
||
37 | private $jsonCompatibilityConverter; |
||
38 | |||
39 | /** |
||
40 | * @var int |
||
41 | */ |
||
42 | private $pageId; |
||
43 | |||
44 | /** |
||
45 | * @var bool |
||
46 | */ |
||
47 | private $CSVExport = false; |
||
48 | |||
49 | /** |
||
50 | * @var InfoModuleController |
||
51 | */ |
||
52 | private $infoModuleController; |
||
53 | |||
54 | /** |
||
55 | * @var QueryBuilder |
||
56 | */ |
||
57 | private $queryBuilder; |
||
58 | |||
59 | /** |
||
60 | * @var CsvWriterInterface |
||
61 | */ |
||
62 | private $csvWriter; |
||
63 | |||
64 | /** |
||
65 | * @var QueueRepository |
||
66 | */ |
||
67 | private $queueRepository; |
||
68 | |||
69 | /** |
||
70 | * @var array |
||
71 | */ |
||
72 | private $CSVaccu = []; |
||
73 | |||
74 | 1 | public function __construct(StandaloneView $view, InfoModuleController $infoModuleController, array $extensionSettings) |
|
75 | { |
||
76 | 1 | $objectManager = GeneralUtility::makeInstance(ObjectManager::class); |
|
77 | 1 | $this->view = $view; |
|
78 | 1 | $this->infoModuleController = $infoModuleController; |
|
79 | 1 | $this->jsonCompatibilityConverter = new JsonCompatibilityConverter(); |
|
80 | 1 | $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(QueueRepository::TABLE_NAME); |
|
81 | 1 | $this->csvWriter = new CrawlerCsvWriter(); |
|
82 | 1 | $this->extensionSettings = $extensionSettings; |
|
83 | 1 | $this->queueRepository = $objectManager->get(QueueRepository::class); |
|
84 | 1 | } |
|
85 | |||
86 | public function render($id, string $elementName, array $menuItems): string |
||
87 | { |
||
88 | $quiPart = GeneralUtility::_GP('qid_details') ? '&qid_details=' . (int) GeneralUtility::_GP('qid_details') : ''; |
||
89 | $setId = (int) GeneralUtility::_GP('setID'); |
||
90 | $this->pageId = $id; |
||
91 | |||
92 | return $this->getDepthDropDownHtml($id, $elementName, $menuItems) |
||
93 | . $this->showLogAction($setId, $quiPart); |
||
94 | } |
||
95 | |||
96 | private function getDepthDropDownHtml($id, string $currentValue, array $menuItems): string |
||
97 | { |
||
98 | return BackendUtility::getFuncMenu( |
||
99 | $id, |
||
100 | 'SET[depth]', |
||
101 | $currentValue, |
||
102 | $menuItems |
||
103 | ); |
||
104 | } |
||
105 | |||
106 | /******************************* |
||
107 | * |
||
108 | * Shows log of indexed URLs |
||
109 | * |
||
110 | ******************************/ |
||
111 | |||
112 | /** |
||
113 | * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException |
||
114 | * @throws \TYPO3\CMS\Extbase\Object\Exception |
||
115 | */ |
||
116 | private function showLogAction(int $setId, string $quiPath): string |
||
117 | { |
||
118 | $this->view->setTemplate('ShowLog'); |
||
119 | if (empty($this->pageId)) { |
||
120 | $this->isErrorDetected = true; |
||
121 | MessageUtility::addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noPageSelected')); |
||
122 | } else { |
||
123 | $this->findCrawler()->setID = GeneralUtility::md5int(microtime()); |
||
124 | |||
125 | $csvExport = GeneralUtility::_POST('_csv'); |
||
126 | $this->CSVExport = isset($csvExport); |
||
127 | |||
128 | // Read URL: |
||
129 | if (GeneralUtility::_GP('qid_read')) { |
||
130 | $this->findCrawler()->readUrl((int) GeneralUtility::_GP('qid_read'), true); |
||
131 | } |
||
132 | |||
133 | // Look for set ID sent - if it is, we will display contents of that set: |
||
134 | $showSetId = (int) GeneralUtility::_GP('setID'); |
||
135 | |||
136 | $queueId = GeneralUtility::_GP('qid_details'); |
||
137 | $this->view->assign('queueId', $queueId); |
||
138 | $this->view->assign('setId', $showSetId); |
||
139 | // Show details: |
||
140 | if ($queueId) { |
||
141 | // Get entry record: |
||
142 | $q_entry = $this->queryBuilder |
||
143 | ->from(QueueRepository::TABLE_NAME) |
||
144 | ->select('*') |
||
145 | ->where( |
||
146 | $this->queryBuilder->expr()->eq('qid', $this->queryBuilder->createNamedParameter($queueId)) |
||
147 | ) |
||
148 | ->execute() |
||
149 | ->fetch(); |
||
150 | |||
151 | // Explode values |
||
152 | $q_entry['parameters'] = $this->jsonCompatibilityConverter->convert($q_entry['parameters']); |
||
153 | $q_entry['result_data'] = $this->jsonCompatibilityConverter->convert($q_entry['result_data']); |
||
154 | $resStatus = ResultHandler::getResStatus($q_entry['result_data']); |
||
155 | if (is_array($q_entry['result_data'])) { |
||
156 | $q_entry['result_data']['content'] = $this->jsonCompatibilityConverter->convert($q_entry['result_data']['content']); |
||
157 | if (! $this->infoModuleController->MOD_SETTINGS['log_resultLog']) { |
||
158 | if (is_array($q_entry['result_data']['content'])) { |
||
159 | unset($q_entry['result_data']['content']['log']); |
||
160 | } |
||
161 | } |
||
162 | } |
||
163 | |||
164 | $this->view->assign('queueStatus', $resStatus); |
||
165 | $this->view->assign('queueDetails', DebugUtility::viewArray($q_entry)); |
||
166 | } else { |
||
167 | // Show list |
||
168 | // Drawing tree: |
||
169 | $tree = GeneralUtility::makeInstance(PageTreeView::class); |
||
170 | $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1); |
||
171 | $tree->init('AND ' . $perms_clause); |
||
172 | |||
173 | // Set root row: |
||
174 | $pageinfo = BackendUtility::readPageAccess( |
||
175 | $this->pageId, |
||
176 | $perms_clause |
||
177 | ); |
||
178 | $HTML = $this->getIconFactory()->getIconForRecord('pages', $pageinfo, Icon::SIZE_SMALL)->render(); |
||
179 | $tree->tree[] = [ |
||
180 | 'row' => $pageinfo, |
||
181 | 'HTML' => $HTML, |
||
182 | ]; |
||
183 | |||
184 | // Get branch beneath: |
||
185 | if ($this->infoModuleController->MOD_SETTINGS['depth']) { |
||
186 | $tree->getTree($this->pageId, $this->infoModuleController->MOD_SETTINGS['depth']); |
||
187 | } |
||
188 | |||
189 | // If Flush button is pressed, flush tables instead of selecting entries: |
||
190 | if (GeneralUtility::_POST('_flush')) { |
||
191 | $doFlush = true; |
||
192 | } elseif (GeneralUtility::_POST('_flush_all')) { |
||
193 | $doFlush = true; |
||
194 | $this->infoModuleController->MOD_SETTINGS['log_display'] = 'all'; |
||
195 | } else { |
||
196 | $doFlush = false; |
||
197 | } |
||
198 | $itemsPerPage = (int) $this->infoModuleController->MOD_SETTINGS['itemsPerPage']; |
||
199 | $queueFilter = new QueueFilter($this->infoModuleController->MOD_SETTINGS['log_display']); |
||
200 | |||
201 | if ($doFlush) { |
||
202 | $this->queueRepository->flushQueue($queueFilter); |
||
203 | } |
||
204 | |||
205 | // Traverse page tree: |
||
206 | $count = 0; |
||
207 | $logEntriesPerPage = []; |
||
208 | foreach ($tree->tree as $data) { |
||
209 | $logEntriesOfPage = $this->queueRepository->getQueueEntriesForPageId( |
||
210 | (int) $data['row']['uid'], |
||
211 | $itemsPerPage, |
||
212 | $queueFilter |
||
213 | ); |
||
214 | |||
215 | $logEntriesPerPage[] = $this->drawLog_addRows( |
||
216 | $logEntriesOfPage, |
||
217 | $data['HTML'] . BackendUtility::getRecordTitle('pages', $data['row'], true) |
||
218 | ); |
||
219 | if (++$count === 1000) { |
||
220 | break; |
||
221 | } |
||
222 | } |
||
223 | |||
224 | $this->view->assign('logEntriesPerPage', $logEntriesPerPage); |
||
225 | } |
||
226 | |||
227 | if ($this->CSVExport) { |
||
228 | $this->outputCsvFile(); |
||
229 | } |
||
230 | } |
||
231 | $this->view->assign('showResultLog', (bool) $this->infoModuleController->MOD_SETTINGS['log_resultLog']); |
||
232 | $this->view->assign('showFeVars', (bool) $this->infoModuleController->MOD_SETTINGS['log_feVars']); |
||
233 | $this->view->assign('displayActions', 1); |
||
234 | $this->view->assign('displayLogFilterHtml', $this->getDisplayLogFilterHtml($setId)); |
||
235 | $this->view->assign('itemPerPageHtml', $this->getItemsPerPageDropDownHtml()); |
||
236 | $this->view->assign('showResultLogHtml', $this->getShowResultLogCheckBoxHtml($setId, $quiPath)); |
||
237 | $this->view->assign('showFeVarsHtml', $this->getShowFeVarsCheckBoxHtml($setId, $quiPath)); |
||
238 | return $this->view->render(); |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * Outputs the CSV file and sets the correct headers |
||
243 | */ |
||
244 | private function outputCsvFile(): void |
||
245 | { |
||
246 | if (! count($this->CSVaccu)) { |
||
247 | MessageUtility::addWarningMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:message.canNotExportEmptyQueueToCsvText')); |
||
248 | return; |
||
249 | } |
||
250 | |||
251 | $csvString = $this->csvWriter->arrayToCsv($this->CSVaccu); |
||
252 | |||
253 | header('Content-Type: application/octet-stream'); |
||
254 | header('Content-Disposition: attachment; filename=CrawlerLog.csv'); |
||
255 | echo $csvString; |
||
256 | |||
257 | exit; |
||
258 | } |
||
259 | |||
260 | private function getIconFactory(): IconFactory |
||
261 | { |
||
262 | return GeneralUtility::makeInstance(IconFactory::class); |
||
263 | } |
||
264 | |||
265 | private function getDisplayLogFilterHtml(int $setId): string |
||
266 | { |
||
267 | return $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.display') . ': ' . BackendUtility::getFuncMenu( |
||
268 | $this->pageId, |
||
269 | 'SET[log_display]', |
||
270 | $this->infoModuleController->MOD_SETTINGS['log_display'], |
||
271 | $this->infoModuleController->MOD_MENU['log_display'], |
||
272 | 'index.php', |
||
273 | '&setID=' . $setId |
||
274 | ); |
||
275 | } |
||
276 | |||
277 | private function getItemsPerPageDropDownHtml(): string |
||
278 | { |
||
279 | return $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.itemsPerPage') . ': ' . |
||
280 | BackendUtility::getFuncMenu( |
||
281 | $this->pageId, |
||
282 | 'SET[itemsPerPage]', |
||
283 | $this->infoModuleController->MOD_SETTINGS['itemsPerPage'], |
||
284 | $this->infoModuleController->MOD_MENU['itemsPerPage'] |
||
285 | ); |
||
286 | } |
||
287 | |||
288 | private function getShowResultLogCheckBoxHtml(int $setId, string $quiPart): string |
||
289 | { |
||
290 | return BackendUtility::getFuncCheck( |
||
291 | $this->pageId, |
||
292 | 'SET[log_resultLog]', |
||
293 | $this->infoModuleController->MOD_SETTINGS['log_resultLog'], |
||
294 | 'index.php', |
||
295 | '&setID=' . $setId . $quiPart |
||
296 | ) . ' ' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.showresultlog'); |
||
297 | } |
||
298 | |||
299 | private function getShowFeVarsCheckBoxHtml(int $setId, string $quiPart): string |
||
300 | { |
||
301 | return BackendUtility::getFuncCheck( |
||
302 | $this->pageId, |
||
303 | 'SET[log_feVars]', |
||
304 | $this->infoModuleController->MOD_SETTINGS['log_feVars'], |
||
305 | 'index.php', |
||
306 | '&setID=' . $setId . $quiPart |
||
307 | ) . ' ' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.showfevars'); |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * Create the rows for display of the page tree |
||
312 | * For each page a number of rows are shown displaying GET variable configuration |
||
313 | * |
||
314 | * @param array $logEntriesOfPage Log items of one page |
||
315 | * @param string $titleString Title string |
||
316 | * |
||
317 | * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException |
||
318 | * |
||
319 | * @psalm-return non-empty-list<array{titleRowSpan: positive-int, colSpan: int, title: string, noEntries?: string, trClass?: string, qid?: array{link: \TYPO3\CMS\Core\Http\Uri, link-text: string}, refresh?: array{link: \TYPO3\CMS\Core\Http\Uri, link-text: Icon, warning: Icon|string}, columns?: array{url: mixed|string, scheduled: string, exec_time: string, result_log: string, result_status: string, feUserGroupList: string, procInstructions: string, set_id: string, tsfe_id: string, tsfe_gr_list: string}}> |
||
320 | */ |
||
321 | private function drawLog_addRows(array $logEntriesOfPage, string $titleString): array |
||
322 | { |
||
323 | $resultArray = []; |
||
324 | $contentArray = []; |
||
325 | |||
326 | $contentArray['titleRowSpan'] = 1; |
||
327 | $contentArray['colSpan'] = 9 |
||
328 | + ($this->infoModuleController->MOD_SETTINGS['log_resultLog'] ? -1 : 0) |
||
329 | + ($this->infoModuleController->MOD_SETTINGS['log_feVars'] ? 3 : 0); |
||
330 | |||
331 | if (! empty($logEntriesOfPage)) { |
||
332 | $setId = (int) GeneralUtility::_GP('setID'); |
||
333 | $refreshIcon = $this->getIconFactory()->getIcon('actions-system-refresh', Icon::SIZE_SMALL); |
||
334 | // Traverse parameter combinations: |
||
335 | $firstIteration = true; |
||
336 | foreach ($logEntriesOfPage as $vv) { |
||
337 | // Title column: |
||
338 | if ($firstIteration) { |
||
339 | $contentArray['titleRowSpan'] = count($logEntriesOfPage); |
||
340 | $contentArray['title'] = $titleString; |
||
341 | } else { |
||
342 | $contentArray['title'] = ''; |
||
343 | $contentArray['titleRowSpan'] = 1; |
||
344 | } |
||
345 | |||
346 | $firstIteration = false; |
||
347 | $execTime = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-'; |
||
348 | |||
349 | // Result: |
||
350 | $resLog = ResultHandler::getResultLog($vv); |
||
351 | |||
352 | $resultData = $vv['result_data'] ? $this->jsonCompatibilityConverter->convert($vv['result_data']) : []; |
||
353 | $resStatus = ResultHandler::getResStatus($resultData); |
||
354 | |||
355 | // Compile row: |
||
356 | $parameters = $this->jsonCompatibilityConverter->convert($vv['parameters']); |
||
357 | |||
358 | // Put data into array: |
||
359 | $rowData = []; |
||
360 | if ($this->infoModuleController->MOD_SETTINGS['log_resultLog']) { |
||
361 | $rowData['result_log'] = $resLog; |
||
362 | } else { |
||
363 | $rowData['scheduled'] = ($vv['scheduled'] > 0) ? BackendUtility::datetime($vv['scheduled']) : 0; |
||
364 | $rowData['exec_time'] = $execTime; |
||
365 | } |
||
366 | $rowData['result_status'] = GeneralUtility::fixed_lgd_cs($resStatus, 50); |
||
367 | $url = htmlspecialchars($parameters['url'] ?? $parameters['alturl'], ENT_QUOTES | ENT_HTML5); |
||
368 | $rowData['url'] = '<a href="' . $url . '" target="_newWIndow">' . $url . '</a>'; |
||
369 | $rowData['feUserGroupList'] = $parameters['feUserGroupList'] ?: ''; |
||
370 | $rowData['procInstructions'] = is_array($parameters['procInstructions']) ? implode('; ', $parameters['procInstructions']) : ''; |
||
371 | $rowData['set_id'] = (string) $vv['set_id']; |
||
372 | |||
373 | if ($this->infoModuleController->MOD_SETTINGS['log_feVars']) { |
||
374 | $resFeVars = ResultHandler::getResFeVars($resultData ?: []); |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
375 | $rowData['tsfe_id'] = $resFeVars['id'] ?: ''; |
||
376 | $rowData['tsfe_gr_list'] = $resFeVars['gr_list'] ?: ''; |
||
377 | } |
||
378 | |||
379 | $trClass = ''; |
||
380 | $warningIcon = ''; |
||
381 | if (str_contains($resStatus, 'Error:')) { |
||
382 | $trClass = 'bg-danger'; |
||
383 | $warningIcon = $this->getIconFactory()->getIcon('actions-ban', Icon::SIZE_SMALL); |
||
384 | } |
||
385 | |||
386 | // Put rows together: |
||
387 | $contentArray['trClass'] = $trClass; |
||
388 | $contentArray['qid'] = [ |
||
389 | 'link' => UrlBuilder::getInfoModuleUrl(['qid_details' => $vv['qid'], 'setID' => $setId]), |
||
390 | 'link-text' => htmlspecialchars((string) $vv['qid'], ENT_QUOTES | ENT_HTML5), |
||
391 | ]; |
||
392 | $contentArray['refresh'] = [ |
||
393 | 'link' => UrlBuilder::getInfoModuleUrl(['qid_read' => $vv['qid'], 'setID' => $setId]), |
||
394 | 'link-text' => $refreshIcon, |
||
395 | 'warning' => $warningIcon, |
||
396 | ]; |
||
397 | |||
398 | foreach ($rowData as $fKey => $value) { |
||
399 | if ($fKey === 'url') { |
||
400 | $contentArray['columns'][$fKey] = $value; |
||
401 | } else { |
||
402 | $contentArray['columns'][$fKey] = nl2br(htmlspecialchars((string) $value, ENT_QUOTES | ENT_HTML5)); |
||
403 | } |
||
404 | } |
||
405 | |||
406 | $resultArray[] = $contentArray; |
||
407 | |||
408 | if ($this->CSVExport) { |
||
409 | // Only for CSV (adding qid and scheduled/exec_time if needed): |
||
410 | $csvExport['scheduled'] = BackendUtility::datetime($vv['scheduled']); |
||
411 | $csvExport['exec_time'] = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-'; |
||
412 | $csvExport['result_status'] = $contentArray['columns']['result_status']; |
||
413 | $csvExport['url'] = $contentArray['columns']['url']; |
||
414 | $csvExport['feUserGroupList'] = $contentArray['columns']['feUserGroupList']; |
||
415 | $csvExport['procInstructions'] = $contentArray['columns']['procInstructions']; |
||
416 | $csvExport['set_id'] = $contentArray['columns']['set_id']; |
||
417 | $csvExport['result_log'] = str_replace(chr(10), '// ', $resLog); |
||
418 | $csvExport['qid'] = $vv['qid']; |
||
419 | $this->CSVaccu[] = $csvExport; |
||
420 | } |
||
421 | } |
||
422 | } else { |
||
423 | $contentArray['title'] = $titleString; |
||
424 | $contentArray['noEntries'] = $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noentries'); |
||
425 | |||
426 | $resultArray[] = $contentArray; |
||
427 | } |
||
428 | |||
429 | return $resultArray; |
||
430 | } |
||
431 | } |
||
432 |