• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tomasnorre / crawler / 3696698329

pending completion
3696698329

Pull #988

github

GitHub
Merge d1d32bd2d into 3f94d6a4e
Pull Request #988: [WIP][FEATURE] Setup new Backend Module

417 of 417 new or added lines in 9 files covered. (100.0%)

1601 of 2523 relevant lines covered (63.46%)

3.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/Classes/Controller/Backend/BackendModuleCrawlerLogController.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace AOE\Crawler\Controller\Backend;
6

7
/*
8
 * (c) 2022-     Tomas Norre Mikkelsen <tomasnorre@gmail.com>
9
 *
10
 * This file is part of the TYPO3 Crawler Extension.
11
 *
12
 * It is free software; you can redistribute it and/or modify it under
13
 * the terms of the GNU General Public License, either version 2
14
 * of the License, or any later version.
15
 *
16
 * For the full copyright and license information, please read the
17
 * LICENSE.txt file that was distributed with this source code.
18
 *
19
 * The TYPO3 project - inspiring people to share!
20
 */
21

22
use AOE\Crawler\Controller\Backend\Helper\ResultHandler;
23
use AOE\Crawler\Controller\Backend\Helper\UrlBuilder;
24
use AOE\Crawler\Converter\JsonCompatibilityConverter;
25
use AOE\Crawler\Domain\Repository\QueueRepository;
26
use AOE\Crawler\Utility\MessageUtility;
27
use AOE\Crawler\Value\QueueFilter;
28
use AOE\Crawler\Writer\FileWriter\CsvWriter\CsvWriterInterface;
29
use Psr\Http\Message\ResponseInterface;
30
use Psr\Http\Message\ServerRequestInterface;
31
use Symfony\Contracts\Service\Attribute\Required;
32
use TYPO3\CMS\Backend\Template\ModuleTemplate;
33
use TYPO3\CMS\Backend\Tree\View\PageTreeView;
34
use TYPO3\CMS\Backend\Utility\BackendUtility;
35
use TYPO3\CMS\Core\Database\ConnectionPool;
36
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
37
use TYPO3\CMS\Core\Imaging\Icon;
38
use TYPO3\CMS\Core\Imaging\IconFactory;
39
use TYPO3\CMS\Core\Utility\DebugUtility;
40
use TYPO3\CMS\Core\Utility\GeneralUtility;
41

42
class BackendModuleCrawlerLogController extends AbstractBackendModuleController implements BackendModuleControllerInterface
43
{
44
    private const BACKEND_MODULE = 'web_site_crawler_log';
45

46
    private QueryBuilder $queryBuilder;
47
    private bool $CSVExport = false;
48
    private array $CSVaccu = [];
49
    private array $backendModuleMenu;
50
    private int $setId;
51
    private string $quiPath;
52
    private string $logDisplay;
53
    private int $itemsPerPage;
54
    private string $showResultLog;
55
    private String $showFeVars;
56
    private int $showSetId;
57
    private string $logDepth;
58

59
    public function __construct(
60
        private QueueRepository $queueRepository,
61
        private CsvWriterInterface $csvWriter,
62
        private JsonCompatibilityConverter $jsonCompatibilityConverter,
63
        private IconFactory $iconFactory
64
    ) {
65
        $this->backendModuleMenu = $this->getModuleMenu();
×
66
    }
67

68
    #[Required]
69
    public function setQueryBuilder(): void
70
    {
71
        $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(
×
72
            QueueRepository::TABLE_NAME
×
73
        );
×
74
    }
75

76
    public function handleRequest(ServerRequestInterface $request): ResponseInterface
77
    {
78
        $this->setPropertiesBasedOnPostVars($request);
×
79
        $this->moduleTemplate = $this->setupView($request, $this->pageUid);
×
80
        $this->moduleTemplate = $this->moduleTemplate->makeDocHeaderModuleMenu(['id' => $this->pageUid]);
×
81

82
        if (!$this->pageUid) {
×
83
            $this->isErrorDetected = true;
×
84
            $this->moduleTemplate->assign('noPageSelected', true);
×
85
            return $this->moduleTemplate->renderResponse('Backend/ShowLog');
×
86
        }
87
        $this->moduleTemplate = $this->assignValues();
×
88

89
        return $this->moduleTemplate->renderResponse('Backend/ShowLog');
×
90
    }
91

92
    public function getQueueEntry(mixed $queueId): array
93
    {
94
        $q_entry = $this->queryBuilder
×
95
            ->from(QueueRepository::TABLE_NAME)
×
96
            ->select('*')
×
97
            ->where($this->queryBuilder->expr()->eq('qid', $this->queryBuilder->createNamedParameter($queueId)))
×
98
            ->execute()
×
99
            ->fetch();
×
100

101
        // Explode values
102
        $q_entry['parameters'] = $this->jsonCompatibilityConverter->convert($q_entry['parameters']);
×
103
        $q_entry['result_data'] = $this->jsonCompatibilityConverter->convert($q_entry['result_data']);
×
104
        $resStatus = ResultHandler::getResStatus($q_entry['result_data']);
×
105
        if (is_array($q_entry['result_data'])) {
×
106
            $q_entry['result_data']['content'] = $this->jsonCompatibilityConverter->convert(
×
107
                $q_entry['result_data']['content']
×
108
            );
×
109
            if (!$this->showResultLog) {
×
110
                if (is_array($q_entry['result_data']['content'])) {
×
111
                    unset($q_entry['result_data']['content']['log']);
×
112
                }
113
            }
114
        }
115
        return [$q_entry, $resStatus];
×
116
    }
117

118
    private function assignValues(): ModuleTemplate
119
    {
120
        // Look for set ID sent - if it is, we will display contents of that set:
121
        $this->showSetId = (int) GeneralUtility::_GP('setID');
×
122

123
        if ($this->queueId) {
×
124
            // Get entry record:
125
            [$q_entry, $resStatus] = $this->getQueueEntry($this->queueId);
×
126
            $this->moduleTemplate->assignMultiple([
×
127
                'queueStatus' => $resStatus,
×
128
                'queueDetails' => DebugUtility::viewArray($q_entry),
×
129
            ]);
×
130
        } else {
131
            // Show list
132
            // Drawing tree:
133
            $tree = GeneralUtility::makeInstance(PageTreeView::class);
×
134
            $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
×
135
            $tree->init('AND ' . $perms_clause);
×
136

137
            // Set root row:
138
            $pageinfo = BackendUtility::readPageAccess($this->pageUid, $perms_clause);
×
139

140
            if (is_array($pageinfo)) {
×
141
                $HTML = $this->iconFactory->getIconForRecord('pages', $pageinfo, Icon::SIZE_SMALL)->render();
×
142
                $tree->tree[] = [
×
143
                    'row' => $pageinfo,
×
144
                    'HTML' => $HTML,
×
145
                ];
×
146
            }
147

148
            // Get branch beneath:
149
            if ($this->logDepth) {
×
150
                $tree->getTree($this->pageUid, $this->logDepth);
×
151
            }
152

153
            // If Flush button is pressed, flush tables instead of selecting entries:
154
            if (GeneralUtility::_POST('_flush')) {
×
155
                $doFlush = true;
×
156
            } elseif (GeneralUtility::_POST('_flush_all')) {
×
157
                $doFlush = true;
×
158
                $this->logDisplay = 'all';
×
159
            } else {
160
                $doFlush = false;
×
161
            }
162
            $queueFilter = new QueueFilter($this->logDisplay);
×
163

164
            if ($doFlush) {
×
165
                $this->queueRepository->flushQueue($queueFilter);
×
166
            }
167

168
            // Traverse page tree:
169
            $count = 0;
×
170
            $logEntriesPerPage = [];
×
171
            foreach ($tree->tree as $data) {
×
172
                $logEntriesOfPage = $this->queueRepository->getQueueEntriesForPageId(
×
173
                    (int) $data['row']['uid'],
×
174
                    $this->itemsPerPage,
×
175
                    $queueFilter
×
176
                );
×
177

178
                $logEntriesPerPage[] = $this->drawLog_addRows(
×
179
                    $logEntriesOfPage,
×
180
                    $data['HTML'] . BackendUtility::getRecordTitle('pages', $data['row'], true)
×
181
                );
×
182
                if (++$count === 1000) {
×
183
                    break;
×
184
                }
185
            }
186

187
            $this->moduleTemplate->assign('logEntriesPerPage', $logEntriesPerPage);
×
188
        }
189

190
        if ($this->CSVExport) {
×
191
            $this->outputCsvFile();
×
192
        }
193

194
        return $this->moduleTemplate->assignMultiple([
×
195
            'actionUrl' => '',
×
196
            'queueId' => $this->queueId,
×
197
            'setId' => $this->showSetId,
×
198
            'noPageSelected' => false,
×
199
            'logEntriesPerPage' => $logEntriesPerPage,
×
200
            'showResultLog' => $this->showResultLog,
×
201
            'showFeVars' => $this->showFeVars,
×
202
            'displayActions' => 1,
×
203
            'displayLogFilterHtml' => $this->getDisplayLogFilterHtml(),
×
204
            'itemPerPageHtml' => $this->getItemsPerPageDropDownHtml(),
×
205
            'showResultLogHtml' => $this->getShowResultLogCheckBoxHtml(),
×
206
            'showFeVarsHtml' => $this->getShowFeVarsCheckBoxHtml(),
×
207
            'depthDropDownHtml' => $this->getDepthDropDownHtml(
×
208
                $this->pageUid,
×
209
                $this->logDepth,
×
210
                $this->backendModuleMenu['depth']
×
211
            ),
×
212
        ]);
×
213
    }
214

215
    private function getDisplayLogFilterHtml(): string
216
    {
217
        return $this->getLanguageService()->sL(
×
218
            'LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.display'
×
219
        ) . ': ' . BackendUtility::getFuncMenu(
×
220
            $this->pageUid,
×
221
            'logDisplay',
×
222
            $this->logDisplay,
×
223
            $this->backendModuleMenu['log_display'],
×
224
            'index.php',
×
225
            $this->getAdditionalQueryParams('logDisplay')
×
226
        );
×
227
    }
228

229
    private function getDepthDropDownHtml(int $id, string $currentValue, array $menuItems): string
230
    {
231
        return BackendUtility::getFuncMenu($id, 'logDepth', $currentValue, $menuItems);
×
232
    }
233

234
    private function getItemsPerPageDropDownHtml(): string
235
    {
236
        return $this->getLanguageService()->sL(
×
237
            'LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.itemsPerPage'
×
238
        ) . ': ' .
×
239
            BackendUtility::getFuncMenu(
×
240
                $this->pageUid,
×
241
                'itemsPerPage',
×
242
                $this->itemsPerPage,
×
243
                $this->backendModuleMenu['itemsPerPage'],
×
244
                'index.php',
×
245
                $this->getAdditionalQueryParams('itemsPerPage')
×
246
            );
×
247
    }
248

249
    private function getShowResultLogCheckBoxHtml(): string
250
    {
251
        return BackendUtility::getFuncCheck(
×
252
            $this->pageUid,
×
253
            'ShowResultLog',
×
254
            $this->showResultLog,
×
255
            'index.php',
×
256
            $this->quiPath . $this->getAdditionalQueryParams('ShowResultLog')
×
257
        ) . '&nbsp;' . $this->getLanguageService()->sL(
×
258
            'LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.showresultlog'
×
259
        );
×
260
    }
261

262
    private function getShowFeVarsCheckBoxHtml(): string
263
    {
264
        return BackendUtility::getFuncCheck(
×
265
            $this->pageUid,
×
266
            'ShowFeVars',
×
267
            $this->showFeVars,
×
268
            'index.php',
×
269
            $this->quiPath . $this->getAdditionalQueryParams('ShowFeVars')
×
270
        ) . '&nbsp;' . $this->getLanguageService()->sL(
×
271
            'LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.showfevars'
×
272
        );
×
273
    }
274

275
    /**
276
     * Create the rows for display of the page tree
277
     * For each page a number of rows are shown displaying GET variable configuration
278
     *
279
     * @param array $logEntriesOfPage Log items of one page
280
     * @param string $titleString Title string
281
     *
282
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
283
     *
284
     * @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}}>
285
     */
286
    private function drawLog_addRows(array $logEntriesOfPage, string $titleString): array
287
    {
288
        $resultArray = [];
×
289
        $contentArray = [];
×
290

291
        $contentArray['titleRowSpan'] = 1;
×
292
        $contentArray['colSpan'] = 9
×
293
            + ($this->showResultLog ? -1 : 0)
×
294
            + ($this->showFeVars ? 3 : 0);
×
295

296
        if (!empty($logEntriesOfPage)) {
×
297
            $setId = (int) GeneralUtility::_GP('setID');
×
298
            $refreshIcon = $this->iconFactory->getIcon('actions-system-refresh', Icon::SIZE_SMALL);
×
299
            // Traverse parameter combinations:
300
            $firstIteration = true;
×
301
            foreach ($logEntriesOfPage as $vv) {
×
302
                // Title column:
303
                if ($firstIteration) {
×
304
                    $contentArray['titleRowSpan'] = count($logEntriesOfPage);
×
305
                    $contentArray['title'] = $titleString;
×
306
                } else {
307
                    $contentArray['title'] = '';
×
308
                    $contentArray['titleRowSpan'] = 1;
×
309
                }
310

311
                $firstIteration = false;
×
312
                $execTime = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-';
×
313

314
                // Result:
315
                $resLog = ResultHandler::getResultLog($vv);
×
316

317
                $resultData = $vv['result_data'] ? $this->jsonCompatibilityConverter->convert($vv['result_data']) : [];
×
318
                $resStatus = ResultHandler::getResStatus($resultData);
×
319

320
                // Compile row:
321
                $parameters = $this->jsonCompatibilityConverter->convert($vv['parameters']);
×
322

323
                // Put data into array:
324
                $rowData = [];
×
325
                if ($this->showResultLog) {
×
326
                    $rowData['result_log'] = $resLog;
×
327
                } else {
328
                    $rowData['scheduled'] = ($vv['scheduled'] > 0) ? BackendUtility::datetime($vv['scheduled']) : '-';
×
329
                    $rowData['exec_time'] = $execTime;
×
330
                }
331
                $rowData['result_status'] = GeneralUtility::fixed_lgd_cs($resStatus, 50);
×
332
                $url = htmlspecialchars((string) ($parameters['url'] ?? $parameters['alturl']), ENT_QUOTES | ENT_HTML5);
×
333
                $rowData['url'] = '<a href="' . $url . '" target="_newWIndow">' . $url . '</a>';
×
334
                $rowData['feUserGroupList'] = $parameters['feUserGroupList'] ?? '';
×
335
                $rowData['procInstructions'] = is_array($parameters['procInstructions']) ? implode(
×
336
                    '; ',
×
337
                    $parameters['procInstructions']
×
338
                ) : '';
×
339
                $rowData['set_id'] = (string) $vv['set_id'];
×
340

341
                if ($this->showFeVars) {
×
342
                    $resFeVars = ResultHandler::getResFeVars($resultData ?: []);
×
343
                    $rowData['tsfe_id'] = $resFeVars['id'] ?? '';
×
344
                    $rowData['tsfe_gr_list'] = $resFeVars['gr_list'] ?? '';
×
345
                }
346

347
                $trClass = '';
×
348
                $warningIcon = '';
×
349
                if (str_contains($resStatus, 'Error:')) {
×
350
                    $trClass = 'bg-danger';
×
351
                    $warningIcon = $this->iconFactory->getIcon('actions-ban', Icon::SIZE_SMALL);
×
352
                }
353

354
                // Put rows together:
355
                $contentArray['trClass'] = $trClass;
×
356
                $contentArray['qid'] = [
×
357
                    'link' => UrlBuilder::getBackendModuleUrl([
×
358
                        'qid_details' => $vv['qid'], 'setID' => $setId, ],
×
359
                        self::BACKEND_MODULE
×
360
                    ),
×
361
                    'link-text' => htmlspecialchars((string) $vv['qid'], ENT_QUOTES | ENT_HTML5),
×
362
                ];
×
363
                $contentArray['refresh'] = [
×
364
                    'link' => UrlBuilder::getBackendModuleUrl([
×
365
                        'qid_read' => $vv['qid'], 'setID' => $setId, ],
×
366
                        self::BACKEND_MODULE
×
367
                    ),
×
368
                    'link-text' => $refreshIcon,
×
369
                    'warning' => $warningIcon,
×
370
                ];
×
371

372
                foreach ($rowData as $fKey => $value) {
×
373
                    if ($fKey === 'url') {
×
374
                        $contentArray['columns'][$fKey] = $value;
×
375
                    } else {
376
                        $contentArray['columns'][$fKey] = nl2br(
×
377
                            htmlspecialchars((string) $value, ENT_QUOTES | ENT_HTML5)
×
378
                        );
×
379
                    }
380
                }
381

382
                $resultArray[] = $contentArray;
×
383

384
                if ($this->CSVExport) {
×
385
                    // Only for CSV (adding qid and scheduled/exec_time if needed):
386
                    $csvExport['scheduled'] = BackendUtility::datetime($vv['scheduled']);
×
387
                    $csvExport['exec_time'] = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-';
×
388
                    $csvExport['result_status'] = $contentArray['columns']['result_status'];
×
389
                    $csvExport['url'] = $contentArray['columns']['url'];
×
390
                    $csvExport['feUserGroupList'] = $contentArray['columns']['feUserGroupList'];
×
391
                    $csvExport['procInstructions'] = $contentArray['columns']['procInstructions'];
×
392
                    $csvExport['set_id'] = $contentArray['columns']['set_id'];
×
393
                    $csvExport['result_log'] = str_replace(chr(10), '// ', $resLog);
×
394
                    $csvExport['qid'] = $vv['qid'];
×
395
                    $this->CSVaccu[] = $csvExport;
×
396
                }
397
            }
398
        } else {
399
            $contentArray['title'] = $titleString;
×
400
            $contentArray['noEntries'] = $this->getLanguageService()->sL(
×
401
                'LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noentries'
×
402
            );
×
403

404
            $resultArray[] = $contentArray;
×
405
        }
406

407
        return $resultArray;
×
408
    }
409

410
    private function setPropertiesBasedOnPostVars(ServerRequestInterface $request): void
411
    {
412
        $this->pageUid = (int) ($request->getQueryParams()['id'] ?? -1);
×
413
        $this->setId = (int) GeneralUtility::_GP('setID');
×
414
        $this->quiPath = GeneralUtility::_GP('qid_details') ? '&qid_details=' . (int) GeneralUtility::_GP('qid_details') : '';
×
415
        $this->queueId = GeneralUtility::_GP('qid_details');
×
416
        $this->logDisplay = GeneralUtility::_GP('logDisplay') ?? 'all';
×
417
        $this->itemsPerPage = (int) (GeneralUtility::_GP('itemsPerPage') ?? 10);
×
418
        $this->showResultLog = (string) (GeneralUtility::_GP('ShowResultLog') ?? 0);
×
419
        $this->showFeVars = (string) (GeneralUtility::_GP('ShowFeVars') ?? 0);
×
420
        $this->logDepth = (string) (GeneralUtility::_GP('logDepth') ?? 0);
×
421
    }
422

423
    /*
424
     * Build query string with affected checkbox/dropdown value removed.
425
     */
426
    private function getAdditionalQueryParams(string $keyToBeRemoved): string
427
    {
428
        $queryString = '';
×
429
        $queryParams = [
×
430
            'setID' => $this->setId,
×
431
            'logDisplay' => $this->logDisplay,
×
432
            'itemsPerPage' => $this->itemsPerPage,
×
433
            'ShowFeVars' => $this->showFeVars,
×
434
            'ShowResultLog' => $this->showResultLog,
×
435
        ];
×
436

437
        unset($queryParams[$keyToBeRemoved]);
×
438
        foreach ($queryParams as $key => $value) {
×
439
            $queryString .= "&{$key}={$value}";
×
440
        }
441
        return $queryString;
×
442
    }
443

444
    /**
445
     * Outputs the CSV file and sets the correct headers
446
     */
447
    private function outputCsvFile(): void
448
    {
449
        if (! count($this->CSVaccu)) {
×
450
            MessageUtility::addWarningMessage(
×
451
                $this->getLanguageService()->sL(
×
452
                    'LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:message.canNotExportEmptyQueueToCsvText'
×
453
                )
×
454
            );
×
455
            return;
×
456
        }
457

458
        $csvString = $this->csvWriter->arrayToCsv($this->CSVaccu);
×
459

460
        header('Content-Type: application/octet-stream');
×
461
        header('Content-Disposition: attachment; filename=CrawlerLog.csv');
×
462
        echo $csvString;
×
463

464
        exit;
×
465
    }
466
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc