Passed
Pull Request — master (#647)
by Tomas Norre
21:43 queued 18:06
created

LogRequestForm::render()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 2
b 0
f 0
nc 2
nop 3
dl 0
loc 8
ccs 0
cts 7
cp 0
crap 6
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AOE\Crawler\Backend\RequestForm;
6
7
use AOE\Crawler\Backend\Helper\UrlBuilder;
8
use AOE\Crawler\Converter\JsonCompatibilityConverter;
9
use AOE\Crawler\Utility\MessageUtility;
10
use AOE\Crawler\Writer\FileWriter\CsvWriter\CrawlerCsvWriter;
11
use AOE\Crawler\Writer\FileWriter\CsvWriter\CsvWriterInterface;
12
use Doctrine\DBAL\Query\QueryBuilder;
13
use TYPO3\CMS\Backend\Tree\View\PageTreeView;
14
use TYPO3\CMS\Backend\Utility\BackendUtility;
15
use TYPO3\CMS\Core\Database\ConnectionPool;
16
use TYPO3\CMS\Core\Imaging\Icon;
17
use TYPO3\CMS\Core\Imaging\IconFactory;
18
use TYPO3\CMS\Core\Localization\LanguageService;
19
use TYPO3\CMS\Core\Utility\DebugUtility;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use TYPO3\CMS\Fluid\View\StandaloneView;
22
use TYPO3\CMS\Info\Controller\InfoModuleController;
23
24
final class LogRequestForm extends AbstractRequestForm implements RequestFormInterface
25
{
26
    /**
27
     * @var StandaloneView
28
     */
29
    private $view;
30
31
    /**
32
     * @var JsonCompatibilityConverter
33
     */
34
    private $jsonCompatibilityConverter;
35
36
    /**
37
     * @var int
38
     */
39
    private $pageId;
40
41
    /**
42
     * @var bool
43
     */
44
    private $CSVExport;
45
46
    /**
47
     * @var InfoModuleController
48
     */
49
    private $infoModuleController;
50
51
    /**
52
     * @var QueryBuilder
53
     */
54
    private $queryBuilder;
55
56
    /**
57
     * @var CsvWriterInterface
58
     */
59
    private $csvWriter;
60
61
    public function __construct(StandaloneView $view, InfoModuleController $infoModuleController)
62
    {
63
        $this->view = $view;
64
        $this->infoModuleController = $infoModuleController;
65
        $this->jsonCompatibilityConverter = new JsonCompatibilityConverter();
66
        $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_crawler_queue');
67
        $this->csvWriter = new CrawlerCsvWriter();
68
    }
69
70
    public function render($id, string $currentValue, array $menuItems): string
71
    {
72
        $quiPart = GeneralUtility::_GP('qid_details') ? '&qid_details=' . (int) GeneralUtility::_GP('qid_details') : '';
73
        $setId = (int) GeneralUtility::_GP('setID');
74
        $this->pageId = $id;
75
76
        return $this->getDepthDropDownHtml($id, $currentValue, $menuItems)
77
            . $this->showLogAction($setId, $quiPart);
78
    }
79
80
    private function getDepthDropDownHtml($id, string $currentValue, array $menuItems): string
81
    {
82
        return BackendUtility::getFuncMenu(
83
            $id,
84
            'SET[depth]',
85
            $currentValue,
86
            $menuItems
87
        );
88
    }
89
90
    /*******************************
91
     *
92
     * Shows log of indexed URLs
93
     *
94
     ******************************/
95
96
    /**
97
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
98
     */
99
    private function showLogAction(int $setId, string $quiPath): string
100
    {
101
        $this->view->setTemplate('ShowLog');
102
        if (empty($this->pageId)) {
103
            $this->isErrorDetected = true;
0 ignored issues
show
Bug Best Practice introduced by
The property isErrorDetected does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
104
            MessageUtility::addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noPageSelected'));
105
        } else {
106
            $this->findCrawler()->setAccessMode('gui');
107
            $this->findCrawler()->setID = GeneralUtility::md5int(microtime());
108
109
            $csvExport = GeneralUtility::_POST('_csv');
110
            $this->CSVExport = isset($csvExport);
111
112
            // Read URL:
113
            if (GeneralUtility::_GP('qid_read')) {
114
                $this->findCrawler()->readUrl((int) GeneralUtility::_GP('qid_read'), true);
115
            }
116
117
            // Look for set ID sent - if it is, we will display contents of that set:
118
            $showSetId = (int) GeneralUtility::_GP('setID');
119
120
            $queueId = GeneralUtility::_GP('qid_details');
121
            $this->view->assign('queueId', $queueId);
122
            $this->view->assign('setId', $showSetId);
123
            // Show details:
124
            if ($queueId) {
125
                // Get entry record:
126
                $q_entry = $this->queryBuilder
127
                    ->from('tx_crawler_queue')
128
                    ->select('*')
129
                    ->where(
130
                        $this->queryBuilder->expr()->eq('qid', $this->queryBuilder->createNamedParameter($queueId))
131
                    )
132
                    ->execute()
133
                    ->fetch();
134
135
                // Explode values
136
                $q_entry['parameters'] = $this->jsonCompatibilityConverter->convert($q_entry['parameters']);
137
                $q_entry['result_data'] = $this->jsonCompatibilityConverter->convert($q_entry['result_data']);
138
                $resStatus = $this->getResStatus($q_entry['result_data']);
139
                if (is_array($q_entry['result_data'])) {
140
                    $q_entry['result_data']['content'] = $this->jsonCompatibilityConverter->convert($q_entry['result_data']['content']);
141
                    if (! $this->infoModuleController->MOD_SETTINGS['log_resultLog']) {
142
                        unset($q_entry['result_data']['content']['log']);
143
                    }
144
                }
145
146
                $this->view->assign('queueStatus', $resStatus);
147
                $this->view->assign('queueDetails', DebugUtility::viewArray($q_entry));
148
            } else {
149
                // Show list
150
                // Drawing tree:
151
                $tree = GeneralUtility::makeInstance(PageTreeView::class);
152
                $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
153
                $tree->init('AND ' . $perms_clause);
154
155
                // Set root row:
156
                $pageinfo = BackendUtility::readPageAccess(
157
                    $this->pageId,
158
                    $perms_clause
159
                );
160
                $HTML = $this->getIconFactory()->getIconForRecord('pages', $pageinfo, Icon::SIZE_SMALL)->render();
161
                $tree->tree[] = [
162
                    'row' => $pageinfo,
163
                    'HTML' => $HTML,
164
                ];
165
166
                // Get branch beneath:
167
                if ($this->infoModuleController->MOD_SETTINGS['depth']) {
168
                    $tree->getTree($this->pageId, $this->infoModuleController->MOD_SETTINGS['depth']);
169
                }
170
171
                // If Flush button is pressed, flush tables instead of selecting entries:
172
                if (GeneralUtility::_POST('_flush')) {
173
                    $doFlush = true;
174
                    $doFullFlush = false;
175
                } elseif (GeneralUtility::_POST('_flush_all')) {
176
                    $doFlush = true;
177
                    $doFullFlush = true;
178
                } else {
179
                    $doFlush = false;
180
                    $doFullFlush = false;
181
                }
182
                $itemsPerPage = (int) $this->infoModuleController->MOD_SETTINGS['itemsPerPage'];
183
                // Traverse page tree:
184
                $code = '';
185
                $count = 0;
186
                foreach ($tree->tree as $data) {
187
                    // Get result:
188
                    $logEntriesOfPage = $this->crawlerController->getLogEntriesForPageId(
189
                        (int) $data['row']['uid'],
190
                        $this->infoModuleController->MOD_SETTINGS['log_display'],
191
                        $doFlush,
192
                        $doFullFlush,
193
                        $itemsPerPage
194
                    );
195
196
                    $code .= $this->drawLog_addRows(
197
                        $logEntriesOfPage,
198
                        $data['HTML'] . BackendUtility::getRecordTitle('pages', $data['row'], true)
199
                    );
200
                    if (++$count === 1000) {
201
                        break;
202
                    }
203
                }
204
                $this->view->assign('code', $code);
205
            }
206
207
            if ($this->CSVExport) {
208
                $this->outputCsvFile();
209
            }
210
        }
211
        $this->view->assign('showResultLog', (bool) $this->infoModuleController->MOD_SETTINGS['log_resultLog']);
212
        $this->view->assign('showFeVars', (bool) $this->infoModuleController->MOD_SETTINGS['log_feVars']);
213
        $this->view->assign('displayActions', 1);
214
        $this->view->assign('displayLogFilterHtml', $this->getDisplayLogFilterHtml($setId));
215
        $this->view->assign('itemPerPageHtml', $this->getItemsPerPageDropDownHtml());
216
        $this->view->assign('showResultLogHtml', $this->getShowResultLogCheckBoxHtml($setId, $quiPath));
217
        $this->view->assign('showFeVarsHtml', $this->getShowFeVarsCheckBoxHtml($setId, $quiPath));
218
        return $this->view->render();
219
    }
220
221
    /**
222
     * Outputs the CSV file and sets the correct headers
223
     */
224
    private function outputCsvFile(): void
225
    {
226
        if (! count($this->CSVaccu)) {
227
            MessageUtility::addWarningMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:message.canNotExportEmptyQueueToCsvText'));
228
            return;
229
        }
230
231
        $csvString = $this->csvWriter->arrayToCsv($this->CSVaccu);
232
233
        header('Content-Type: application/octet-stream');
234
        header('Content-Disposition: attachment; filename=CrawlerLog.csv');
235
        echo $csvString;
236
237
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
238
    }
239
240
    private function getIconFactory(): IconFactory
241
    {
242
        return GeneralUtility::makeInstance(IconFactory::class);
243
    }
244
245
    private function getLanguageService(): LanguageService
246
    {
247
        return $GLOBALS['LANG'];
248
    }
249
250
    private function getDisplayLogFilterHtml(int $setId): string
251
    {
252
        return $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.display') . ': ' . BackendUtility::getFuncMenu(
253
                $this->pageId,
254
                'SET[log_display]',
255
                $this->infoModuleController->MOD_SETTINGS['log_display'],
256
                $this->infoModuleController->MOD_MENU['log_display'],
257
                'index.php',
258
                '&setID=' . $setId
259
            );
260
    }
261
262
    private function getItemsPerPageDropDownHtml(): string
263
    {
264
        return $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.itemsPerPage') . ': ' .
265
            BackendUtility::getFuncMenu(
266
                $this->pageId,
267
                'SET[itemsPerPage]',
268
                $this->infoModuleController->MOD_SETTINGS['itemsPerPage'],
269
                $this->infoModuleController->MOD_MENU['itemsPerPage']
270
            );
271
    }
272
273
    private function getShowResultLogCheckBoxHtml(int $setId, string $quiPart): string
274
    {
275
        return BackendUtility::getFuncCheck(
276
                $this->pageId,
277
                'SET[log_resultLog]',
278
                $this->infoModuleController->MOD_SETTINGS['log_resultLog'],
279
                'index.php',
280
                '&setID=' . $setId . $quiPart
281
            ) . '&nbsp;' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.showresultlog');
282
    }
283
284
    private function getShowFeVarsCheckBoxHtml(int $setId, string $quiPart): string
285
    {
286
        return BackendUtility::getFuncCheck(
287
                $this->pageId,
288
                'SET[log_feVars]',
289
                $this->infoModuleController->MOD_SETTINGS['log_feVars'],
290
                'index.php',
291
                '&setID=' . $setId . $quiPart
292
            ) . '&nbsp;' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.showfevars');
293
    }
294
295
    /**
296
     * Create the rows for display of the page tree
297
     * For each page a number of rows are shown displaying GET variable configuration
298
     *
299
     * @param array $logEntriesOfPage Log items of one page
300
     * @param string $titleString Title string
301
     * @return string HTML <tr> content (one or more)
302
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
303
     */
304
    private function drawLog_addRows(array $logEntriesOfPage, string $titleString): string
305
    {
306
        $colSpan = 9
307
            + ($this->infoModuleController->MOD_SETTINGS['log_resultLog'] ? -1 : 0)
308
            + ($this->infoModuleController->MOD_SETTINGS['log_feVars'] ? 3 : 0);
309
310
        if (! empty($logEntriesOfPage)) {
311
            $setId = (int) GeneralUtility::_GP('setID');
312
            $refreshIcon = $this->getIconFactory()->getIcon('actions-system-refresh', Icon::SIZE_SMALL);
313
            // Traverse parameter combinations:
314
            $c = 0;
315
            $content = '';
316
            foreach ($logEntriesOfPage as $vv) {
317
                // Title column:
318
                if (! $c) {
319
                    $titleClm = '<td rowspan="' . count($logEntriesOfPage) . '">' . $titleString . '</td>';
320
                } else {
321
                    $titleClm = '';
322
                }
323
324
                // Result:
325
                $resLog = $this->getResultLog($vv);
326
327
                $resultData = $vv['result_data'] ? $this->jsonCompatibilityConverter->convert($vv['result_data']) : [];
328
                $resStatus = $this->getResStatus($resultData);
329
330
                // Compile row:
331
                $parameters = $this->jsonCompatibilityConverter->convert($vv['parameters']);
332
333
                // Put data into array:
334
                $rowData = [];
335
                if ($this->infoModuleController->MOD_SETTINGS['log_resultLog']) {
336
                    $rowData['result_log'] = $resLog;
337
                } else {
338
                    $rowData['scheduled'] = ($vv['scheduled'] > 0) ? BackendUtility::datetime($vv['scheduled']) : ' ' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.immediate');
339
                    $rowData['exec_time'] = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-';
340
                }
341
                $rowData['result_status'] = GeneralUtility::fixed_lgd_cs($resStatus, 50);
342
                $url = htmlspecialchars($parameters['url'] ?? $parameters['alturl'], ENT_QUOTES | ENT_HTML5);
343
                $rowData['url'] = '<a href="' . $url . '" target="_newWIndow">' . $url . '</a>';
344
                $rowData['feUserGroupList'] = $parameters['feUserGroupList'] ?: '';
345
                $rowData['procInstructions'] = is_array($parameters['procInstructions']) ? implode('; ', $parameters['procInstructions']) : '';
346
                $rowData['set_id'] = (string) $vv['set_id'];
347
348
                if ($this->infoModuleController->MOD_SETTINGS['log_feVars']) {
349
                    $resFeVars = $this->getResFeVars($resultData ?: []);
0 ignored issues
show
Bug introduced by
It seems like $resultData ?: array() can also be of type true; however, parameter $resultData of AOE\Crawler\Backend\Requ...estForm::getResFeVars() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

349
                    $resFeVars = $this->getResFeVars(/** @scrutinizer ignore-type */ $resultData ?: []);
Loading history...
350
                    $rowData['tsfe_id'] = $resFeVars['id'] ?: '';
351
                    $rowData['tsfe_gr_list'] = $resFeVars['gr_list'] ?: '';
352
                    $rowData['tsfe_no_cache'] = $resFeVars['no_cache'] ?: '';
353
                }
354
355
                $trClass = '';
356
                $warningIcon = '';
357
                if ($rowData['exec_time'] !== 0 && $resultData === false) {
358
                    $trClass = 'class="bg-danger"';
359
                    $warningIcon = $this->getIconFactory()->getIcon('actions-ban', Icon::SIZE_SMALL);
360
                }
361
362
                // Put rows together:
363
                $content .= '
364
                    <tr ' . $trClass . ' >
365
                        ' . $titleClm . '
366
                        <td><a href="' . UrlBuilder::getInfoModuleUrl(['qid_details' => $vv['qid'], 'setID' => $setId]) . '">' . htmlspecialchars((string) $vv['qid']) . '</a></td>
367
                        <td><a href="' . UrlBuilder::getInfoModuleUrl(['qid_read' => $vv['qid'], 'setID' => $setId]) . '">' . $refreshIcon . '</a>&nbsp;&nbsp;' . $warningIcon . '</td>';
368
                foreach ($rowData as $fKey => $value) {
369
                    if ($fKey === 'url') {
370
                        $content .= '<td>' . $value . '</td>';
371
                    } else {
372
                        $content .= '<td>' . nl2br(htmlspecialchars(strval($value))) . '</td>';
373
                    }
374
                }
375
                $content .= '</tr>';
376
                $c++;
377
378
                if ($this->CSVExport) {
379
                    // Only for CSV (adding qid and scheduled/exec_time if needed):
380
                    $rowData['result_log'] = implode('// ', explode(chr(10), $resLog));
381
                    $rowData['qid'] = $vv['qid'];
382
                    $rowData['scheduled'] = BackendUtility::datetime($vv['scheduled']);
383
                    $rowData['exec_time'] = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-';
384
                    $this->CSVaccu[] = $rowData;
0 ignored issues
show
Bug Best Practice introduced by
The property CSVaccu does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
385
                }
386
            }
387
        } else {
388
            // Compile row:
389
            $content = '
390
                <tr>
391
                    <td>' . $titleString . '</td>
392
                    <td colspan="' . $colSpan . '"><em>' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noentries') . '</em></td>
393
                </tr>';
394
        }
395
396
        return $content;
397
    }
398
399
    /**
400
     * Extract the log information from the current row and retrieve it as formatted string.
401
     *
402
     * @param array $resultRow
403
     * @return string
404
     */
405
    private function getResultLog($resultRow)
406
    {
407
        $content = '';
408
        if (is_array($resultRow) && array_key_exists('result_data', $resultRow)) {
409
            $requestContent = $this->jsonCompatibilityConverter->convert($resultRow['result_data']) ?: ['content' => ''];
410
            if (! array_key_exists('content', $requestContent)) {
0 ignored issues
show
Bug introduced by
It seems like $requestContent can also be of type true; however, parameter $search of array_key_exists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

410
            if (! array_key_exists('content', /** @scrutinizer ignore-type */ $requestContent)) {
Loading history...
411
                return $content;
412
            }
413
            $requestResult = $this->jsonCompatibilityConverter->convert($requestContent['content']);
414
415
            if (is_array($requestResult) && array_key_exists('log', $requestResult)) {
416
                $content = implode(chr(10), $requestResult['log']);
417
            }
418
        }
419
        return $content;
420
    }
421
422
    private function getResStatus($requestContent): string
423
    {
424
        if (empty($requestContent)) {
425
            return '-';
426
        }
427
        if (! array_key_exists('content', $requestContent)) {
428
            return 'Content index does not exists in requestContent array';
429
        }
430
431
        $requestResult = $this->jsonCompatibilityConverter->convert($requestContent['content']);
432
        if (is_array($requestResult)) {
433
            if (empty($requestResult['errorlog'])) {
434
                return 'OK';
435
            }
436
            return implode("\n", $requestResult['errorlog']);
437
        }
438
439
        if (is_bool($requestResult)) {
0 ignored issues
show
introduced by
The condition is_bool($requestResult) is always true.
Loading history...
440
            return 'Error - no info, sorry!';
441
        }
442
443
        return 'Error: ' . substr(preg_replace('/\s+/', ' ', strip_tags($requestResult)), 0, 10000) . '...';
444
    }
445
446
    /**
447
     * Find Fe vars
448
     */
449
    private function getResFeVars(array $resultData): array
450
    {
451
        if (empty($resultData)) {
452
            return [];
453
        }
454
        $requestResult = $this->jsonCompatibilityConverter->convert($resultData['content']);
455
        return $requestResult['vars'] ?? [];
456
    }
457
}
458