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

tomasnorre / crawler / 17121951236

21 Aug 2025 08:48AM UTC coverage: 69.077% (-0.05%) from 69.129%
17121951236

push

github

tomasnorre
[RELEASE] Crawler version 12.0.9

Added
* Document setup for indexed_search + crawler [@cweiske](https://github.com/cweiske)

Fixed
* Fix usage of no_cache in CrawlerInitialization [@luisbecker-gh](https://github.com/luisbecker-gh)
* Ensure that pages are added to queue on clear page cache [@tomasnorre](https://github.com/tomasnorre)
* Ensure that selections are kept in Crawler Start module, when Updating URLs [@tomasnorre](https://github.com/tomasnorre)
* Ensure that pageRow['uid'] is set or return with skipMessage [@tomasnorre](https://github.com/tomasnorre)
* Back link in Crawler Log after visiting details about Queue Item [@tomasnorre](https://github.com/tomasnorre)

1872 of 2710 relevant lines covered (69.08%)

3.29 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\Controller\CrawlerController;
25
use AOE\Crawler\Converter\JsonCompatibilityConverter;
26
use AOE\Crawler\Domain\Repository\QueueRepository;
27
use AOE\Crawler\Service\BackendModuleLogService;
28
use AOE\Crawler\Service\BackendModuleScriptUrlService;
29
use AOE\Crawler\Utility\MessageUtility;
30
use AOE\Crawler\Value\QueueFilter;
31
use AOE\Crawler\Writer\FileWriter\CsvWriter\CsvWriterInterface;
32
use Psr\Http\Message\ResponseInterface;
33
use Psr\Http\Message\ServerRequestInterface;
34
use Symfony\Contracts\Service\Attribute\Required;
35
use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
36
use TYPO3\CMS\Backend\Template\ModuleTemplate;
37
use TYPO3\CMS\Backend\Tree\View\PageTreeView;
38
use TYPO3\CMS\Backend\Utility\BackendUtility;
39
use TYPO3\CMS\Core\Database\ConnectionPool;
40
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
41
use TYPO3\CMS\Core\Http\Uri;
42
use TYPO3\CMS\Core\Imaging\Icon;
43
use TYPO3\CMS\Core\Imaging\IconFactory;
44
use TYPO3\CMS\Core\Utility\DebugUtility;
45
use TYPO3\CMS\Core\Utility\GeneralUtility;
46

47
/**
48
 * @internal since v12.0.0
49
 */
50
final class BackendModuleCrawlerLogController extends AbstractBackendModuleController implements BackendModuleControllerInterface
51
{
52
    public const BACKEND_MODULE = 'web_site_crawler_log';
53

54
    private QueryBuilder $queryBuilder;
55
    private bool $CSVExport = false;
56
    private readonly array $backendModuleMenu;
57
    private int $setId;
58
    private string $quiPath;
59
    private string $logDisplay;
60
    private int $itemsPerPage;
61
    private string $showResultLog;
62
    private string $showFeVars;
63
    private int $showSetId;
64
    private string $logDepth;
65
    /**
66
     * @var mixed|string|null
67
     */
68
    private mixed $queueId;
69

70
    public function __construct(
71
        private readonly QueueRepository $queueRepository,
72
        private readonly CsvWriterInterface $csvWriter,
73
        private readonly JsonCompatibilityConverter $jsonCompatibilityConverter,
74
        private readonly IconFactory $iconFactory,
75
        private readonly CrawlerController $crawlerController,
76
        private readonly BackendModuleLogService $backendModuleLogService,
77
        private readonly BackendModuleScriptUrlService $backendModuleScriptUrlService,
78
    ) {
79
        $this->backendModuleMenu = $this->getModuleMenu();
×
80
    }
81

82
    #[Required]
83
    public function setQueryBuilder(): void
84
    {
85
        $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(
×
86
            QueueRepository::TABLE_NAME
×
87
        );
×
88
    }
89

90
    public function handleRequest(ServerRequestInterface $request): ResponseInterface
91
    {
92
        $this->setPropertiesBasedOnPostVars($request);
×
93
        $this->moduleTemplate = $this->setupView($request, $this->pageUid);
×
94
        $this->moduleTemplate = $this->moduleTemplate->makeDocHeaderModuleMenu([
×
95
            'id' => $this->pageUid,
×
96
        ]);
×
97

98
        if (!$this->pageUid) {
×
99
            $this->isErrorDetected = true;
×
100
            $this->moduleTemplate->assign('noPageSelected', true);
×
101
            return $this->moduleTemplate->renderResponse('Backend/ShowLog');
×
102
        }
103
        $this->moduleTemplate = $this->assignValues($request);
×
104
        return $this->moduleTemplate->renderResponse('Backend/ShowLog');
×
105
    }
106

107
    private function getQueueEntry(mixed $queueId): array
108
    {
109
        $q_entry = $this->queryBuilder
×
110
            ->from(QueueRepository::TABLE_NAME)
×
111
            ->select('*')->where(
×
112
                $this->queryBuilder->expr()->eq('qid', $this->queryBuilder->createNamedParameter($queueId))
×
113
            )->executeQuery()
×
114
            ->fetchAssociative();
×
115

116
        // Explode values
117
        $q_entry['parameters'] = $this->jsonCompatibilityConverter->convert($q_entry['parameters']);
×
118
        $q_entry['result_data'] = $this->jsonCompatibilityConverter->convert($q_entry['result_data']);
×
119
        $resStatus = ResultHandler::getResStatus($q_entry['result_data']);
×
120
        if (is_array($q_entry['result_data'])) {
×
121
            $q_entry['result_data']['content'] = $this->jsonCompatibilityConverter->convert(
×
122
                $q_entry['result_data']['content']
×
123
            );
×
124
            if (!$this->showResultLog) {
×
125
                if (is_array($q_entry['result_data']['content'])) {
×
126
                    unset($q_entry['result_data']['content']['log']);
×
127
                }
128
            }
129
        }
130
        return [$q_entry, $resStatus];
×
131
    }
132

133
    /**
134
     * @throws RouteNotFoundException
135
     */
136
    private function assignValues(ServerRequestInterface $request): ModuleTemplate
137
    {
138
        // Look for set ID sent - if it is, we will display contents of that set:
139
        $this->showSetId = (int) ($request->getParsedBody()['setID'] ?? $request->getQueryParams()['setID'] ?? 0);
×
140
        $this->CSVExport = (bool) ($request->getParsedBody()['_csv'] ?? $request->getQueryParams()['_csv'] ?? false);
×
141
        $logEntriesPerPage = [];
×
142
        $csvData = [];
×
143

144
        $quidRead = (int) ($request->getParsedBody()['qid_read'] ?? $request->getQueryParams()['qid_read'] ?? 0);
×
145
        if ($quidRead) {
×
146
            $this->crawlerController->readUrl($quidRead, true);
×
147
        }
148

149
        if ($this->queueId) {
×
150
            // Get entry record:
151
            [$q_entry, $resStatus] = $this->getQueueEntry($this->queueId);
×
152
            $this->moduleTemplate->assignMultiple([
×
153
                'queueStatus' => $resStatus,
×
154
                'queueDetails' => DebugUtility::viewArray($q_entry),
×
155
            ]);
×
156
        } else {
157
            // Show list
158
            // Drawing tree:
159
            $tree = GeneralUtility::makeInstance(PageTreeView::class);
×
160
            $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
×
161
            $tree->init('AND ' . $perms_clause);
×
162

163
            // Set root row:
164
            $pageinfo = BackendUtility::readPageAccess($this->pageUid, $perms_clause);
×
165

166
            if (is_array($pageinfo)) {
×
167
                $HTML = $this->iconFactory->getIconForRecord('pages', $pageinfo, Icon::SIZE_SMALL)->render();
×
168
                $tree->tree[] = [
×
169
                    'row' => $pageinfo,
×
170
                    'HTML' => $HTML,
×
171
                ];
×
172
            }
173

174
            // Get branch beneath:
175
            if ($this->logDepth) {
×
176
                $tree->getTree($this->pageUid, (int) $this->logDepth);
×
177
            }
178

179
            // If Flush button is pressed, flush tables instead of selecting entries:
180
            if ($request->getParsedBody()['_flush'] ?? false) {
×
181
                $doFlush = true;
×
182
            } elseif ($request->getParsedBody()['_flush_all'] ?? false) {
×
183
                $doFlush = true;
×
184
                $this->logDisplay = 'all';
×
185
            } else {
186
                $doFlush = false;
×
187
            }
188

189
            $queueFilter = new QueueFilter($this->logDisplay);
×
190

191
            if ($doFlush) {
×
192
                $this->queueRepository->flushQueue($queueFilter);
×
193
            }
194

195
            // Traverse page tree:
196
            $count = 0;
×
197

198
            foreach ($tree->tree as $data) {
×
199
                $logEntriesOfPage = $this->queueRepository->getQueueEntriesForPageId(
×
200
                    (int) $data['row']['uid'],
×
201
                    $this->itemsPerPage,
×
202
                    $queueFilter
×
203
                );
×
204

205
                [$logEntriesPerPage[], $row] = $this->backendModuleLogService->addRows(
×
206
                    $logEntriesOfPage,
×
207
                    (int) ($request->getParsedBody()['setID'] ?? $request->getQueryParams()['setID'] ?? 0),
×
208
                    $data['HTML'] . BackendUtility::getRecordTitle('pages', $data['row'], true),
×
209
                    $this->showResultLog,
×
210
                    $this->showFeVars,
×
211
                    $this->CSVExport
×
212
                );
×
213
                $csvData[] = $row;
×
214

215
                if (++$count === 1000) {
×
216
                    break;
×
217
                }
218
            }
219

220
            $this->moduleTemplate->assign('logEntriesPerPage', $logEntriesPerPage);
×
221
        }
222

223
        if ($this->CSVExport) {
×
224
            $this->outputCsvFile($csvData);
×
225
        }
226

227
        $queryParams = [
×
228
            'setID' => $this->setId,
×
229
            'displayLog' => $this->logDisplay,
×
230
            'itemsPerPage' => $this->itemsPerPage,
×
231
            'ShowFeVars' => $this->showFeVars,
×
232
            'ShowResultLog' => $this->showResultLog,
×
233
            'logDepth' => $this->logDepth,
×
234
        ];
×
235

236
        return $this->moduleTemplate->assignMultiple([
×
237
            'backendModuleUrl' => $this->getBackendModuleUrl(),
×
238
            'actionUrl' => '',
×
239
            'queueId' => $this->queueId,
×
240
            'setId' => $this->showSetId,
×
241
            'noPageSelected' => false,
×
242
            'logEntriesPerPage' => $logEntriesPerPage,
×
243
            'showResultLog' => $this->showResultLog,
×
244
            'showFeVars' => $this->showFeVars,
×
245
            'displayActions' => 1,
×
246
            'displayLogFilterConfig' => [
×
247
                'name' => 'displayLog',
×
248
                'currentValue' => $this->logDisplay,
×
249
                'menuItems' => $this->backendModuleMenu['displayLog'],
×
250
                'scriptUrl' => $this->backendModuleScriptUrlService->buildScriptUrl(
×
251
                    $request,
×
252
                    'displayLog',
×
253
                    $this->pageUid,
×
254
                    $queryParams
×
255
                ),
×
256
            ],
×
257
            'itemPerPageConfig' => [
×
258
                'name' => 'itemsPerPage',
×
259
                'currentValue' => $this->itemsPerPage,
×
260
                'menuItems' => $this->backendModuleMenu['itemsPerPage'],
×
261
                'scriptUrl' => $this->backendModuleScriptUrlService->buildScriptUrl(
×
262
                    $request,
×
263
                    'itemsPerPage',
×
264
                    $this->pageUid,
×
265
                    $queryParams
×
266
                ),
×
267
            ],
×
268
            'showResultLogConfig' => [
×
269
                'name' => 'ShowResultLog',
×
270
                'currentValue' => $this->showResultLog,
×
271
                'scriptUrl' => $this->backendModuleScriptUrlService->buildScriptUrl(
×
272
                    $request,
×
273
                    'ShowResultLog',
×
274
                    $this->pageUid,
×
275
                    $queryParams,
×
276
                    $this->quiPath
×
277
                ),
×
278
            ],
×
279
            'showFeVarsConfig' => [
×
280
                'name' => 'ShowFeVars',
×
281
                'currentValue' => $this->showFeVars,
×
282
                'scriptUrl' => $this->backendModuleScriptUrlService->buildScriptUrl(
×
283
                    $request,
×
284
                    'ShowFeVars',
×
285
                    $this->pageUid,
×
286
                    $queryParams,
×
287
                    $this->quiPath
×
288
                ),
×
289
            ],
×
290
            'depthDropDownConfig' => [
×
291
                'name' => 'logDepth',
×
292
                'currentValue' => $this->logDepth,
×
293
                'menuItems' => $this->backendModuleMenu['logDepth'],
×
294
                'scriptUrl' => $this->backendModuleScriptUrlService->buildScriptUrl(
×
295
                    $request,
×
296
                    'logDepth',
×
297
                    $this->pageUid,
×
298
                    $queryParams
×
299
                ),
×
300
            ],
×
301
        ]);
×
302
    }
303

304
    private function setPropertiesBasedOnPostVars(ServerRequestInterface $request): void
305
    {
306
        $this->pageUid = (int) ($request->getQueryParams()['id'] ?? -1);
×
307
        $this->setId = (int) ($request->getParsedBody()['setID'] ?? $request->getQueryParams()['setID'] ?? 0);
×
308
        $quidDetails = $request->getParsedBody()['qid_details'] ?? $request->getQueryParams()['qid_details'] ?? null;
×
309
        $this->quiPath = $quidDetails ? '&qid_details=' . (int) $quidDetails : '';
×
310
        $this->queueId = $quidDetails ?? null;
×
311
        $this->logDisplay = $request->getParsedBody()['displayLog'] ?? $request->getQueryParams()['displayLog'] ?? 'all';
×
312
        $this->itemsPerPage = (int) ($request->getParsedBody()['itemsPerPage'] ?? $request->getQueryParams()['itemsPerPage'] ?? 10);
×
313
        $this->showResultLog = (string) ($request->getParsedBody()['ShowResultLog'] ?? $request->getQueryParams()['ShowResultLog'] ?? 0);
×
314
        $this->showFeVars = (string) ($request->getParsedBody()['ShowFeVars'] ?? $request->getQueryParams()['ShowFeVars'] ?? 0);
×
315
        $this->logDepth = (string) ($request->getParsedBody()['logDepth'] ?? $request->getQueryParams()['logDepth'] ?? 0);
×
316
    }
317

318
    /**
319
     * Outputs the CSV file and sets the correct headers
320
     */
321
    private function outputCsvFile(array $csvData): void
322
    {
323
        if (!count($csvData)) {
×
324
            MessageUtility::addWarningMessage(
×
325
                $this->getLanguageService()->sL(
×
326
                    'LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:message.canNotExportEmptyQueueToCsvText'
×
327
                )
×
328
            );
×
329
            return;
×
330
        }
331

332
        $csvString = $this->csvWriter->arrayToCsv($csvData);
×
333

334
        header('Content-Type: application/octet-stream');
×
335
        header('Content-Disposition: attachment; filename=CrawlerLog.csv');
×
336
        echo $csvString;
×
337

338
        exit;
×
339
    }
340

341
    private function getBackendModuleUrl(): Uri
342
    {
343
        return GeneralUtility::makeInstance(UrlBuilder::class)->getBackendModuleUrl([], self::BACKEND_MODULE);
×
344
    }
345
}
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

© 2026 Coveralls, Inc