Completed
Push — remove-deprecations ( 88d9f5...992d8c )
by Tomas Norre
19:23 queued 04:05
created

BackendModule::getDisplayLogFilterHtml()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 1
dl 0
loc 9
ccs 0
cts 1
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AOE\Crawler\Backend;
6
7
/***************************************************************
8
 *  Copyright notice
9
 *
10
 *  (c) 2020 AOE GmbH <[email protected]>
11
 *
12
 *  All rights reserved
13
 *
14
 *  This script is part of the TYPO3 project. The TYPO3 project is
15
 *  free software; you can redistribute it and/or modify
16
 *  it under the terms of the GNU General Public License as published by
17
 *  the Free Software Foundation; either version 3 of the License, or
18
 *  (at your option) any later version.
19
 *
20
 *  The GNU General Public License can be found at
21
 *  http://www.gnu.org/copyleft/gpl.html.
22
 *
23
 *  This script is distributed in the hope that it will be useful,
24
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26
 *  GNU General Public License for more details.
27
 *
28
 *  This copyright notice MUST APPEAR in all copies of the script!
29
 ***************************************************************/
30
31
use AOE\Crawler\Configuration\ExtensionConfigurationProvider;
32
use AOE\Crawler\Controller\CrawlerController;
33
use AOE\Crawler\Domain\Model\Reason;
34
use AOE\Crawler\Domain\Repository\ProcessRepository;
35
use AOE\Crawler\Domain\Repository\QueueRepository;
36
use AOE\Crawler\Hooks\CrawlerHookInterface;
37
use AOE\Crawler\Service\ProcessService;
38
use AOE\Crawler\Utility\MessageUtility;
39
use AOE\Crawler\Utility\PhpBinaryUtility;
40
use AOE\Crawler\Utility\SignalSlotUtility;
41
use Psr\Http\Message\UriInterface;
42
use TYPO3\CMS\Backend\Routing\UriBuilder;
43
use TYPO3\CMS\Backend\Template\ModuleTemplate;
44
use TYPO3\CMS\Backend\Tree\View\PageTreeView;
45
use TYPO3\CMS\Backend\Utility\BackendUtility;
46
use TYPO3\CMS\Core\Database\ConnectionPool;
47
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
48
use TYPO3\CMS\Core\Http\Uri;
49
use TYPO3\CMS\Core\Imaging\Icon;
50
use TYPO3\CMS\Core\Imaging\IconFactory;
51
use TYPO3\CMS\Core\Localization\LanguageService;
52
use TYPO3\CMS\Core\Utility\CommandUtility;
53
use TYPO3\CMS\Core\Utility\CsvUtility;
54
use TYPO3\CMS\Core\Utility\DebugUtility;
55
use TYPO3\CMS\Core\Utility\GeneralUtility;
56
use TYPO3\CMS\Core\Utility\MathUtility;
57
use TYPO3\CMS\Extbase\Object\ObjectManager;
58
use TYPO3\CMS\Fluid\View\StandaloneView;
59
use TYPO3\CMS\Info\Controller\InfoModuleController;
60
61
/**
62
 * Function for Info module, containing three main actions:
63
 * - List of all queued items
64
 * - Log functionality
65
 * - Process overview
66
 */
67
class BackendModule
68
{
69
    /**
70
     * @var InfoModuleController Contains a reference to the parent calling object
71
     */
72
    protected $pObj;
73
74
    /**
75
     * The current page ID
76
     * @var int
77
     */
78
    protected $id;
79
80
    // Internal, dynamic:
81
82
    /**
83
     * @var array
84
     */
85
    protected $duplicateTrack = [];
86
87
    /**
88
     * @var bool
89
     */
90
    protected $submitCrawlUrls = false;
91
92
    /**
93
     * @var bool
94
     */
95
    protected $downloadCrawlUrls = false;
96
97
    /**
98
     * @var int
99
     */
100
    protected $scheduledTime = 0;
101
102
    /**
103
     * @var int
104
     */
105
    protected $reqMinute = 1000;
106
107
    /**
108
     * @var array holds the selection of configuration from the configuration selector box
109
     */
110
    protected $incomingConfigurationSelection = [];
111
112
    /**
113
     * @var CrawlerController
114
     */
115
    protected $crawlerController;
116
117
    /**
118
     * @var array
119
     */
120
    protected $CSVaccu = [];
121
122
    /**
123
     * If true the user requested a CSV export of the queue
124
     *
125
     * @var boolean
126
     */
127
    protected $CSVExport = false;
128
129
    /**
130
     * @var array
131
     */
132
    protected $downloadUrls = [];
133
134
    /**
135
     * Holds the configuration from ext_conf_template loaded by getExtensionConfiguration()
136
     *
137
     * @var array
138
     */
139
    protected $extensionSettings = [];
140
141
    /**
142
     * Indicate that an flash message with an error is present.
143
     *
144
     * @var boolean
145
     */
146
    protected $isErrorDetected = false;
147
148
    /**
149
     * @var ProcessService
150
     */
151
    protected $processManager;
152
153
    /**
154
     * @var QueryBuilder
155
     */
156
    protected $queryBuilder;
157
158
    /**
159
     * @var QueueRepository
160
     */
161
    protected $queueRepository;
162
163
    /**
164
     * @var StandaloneView
165
     */
166
    protected $view;
167
168
    /**
169
     * @var IconFactory
170
     */
171
    protected $iconFactory;
172
173
    public function __construct()
174
    {
175
        $objectManger = GeneralUtility::makeInstance(ObjectManager::class);
176
        $this->processManager = $objectManger->get(ProcessService::class);
177
        $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_crawler_queue');
178
        $this->queueRepository = $objectManger->get(QueueRepository::class);
179
        $this->initializeView();
180
        $this->extensionSettings = GeneralUtility::makeInstance(ExtensionConfigurationProvider::class)->getExtensionConfiguration();
181
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
182
    }
183
184
    /**
185
     * Called by the InfoModuleController
186
     */
187
    public function init(InfoModuleController $pObj): void
188
    {
189
        $this->pObj = $pObj;
190
        $this->id = (int) GeneralUtility::_GP('id');
191
        // Setting MOD_MENU items as we need them for logging:
192
        $this->pObj->MOD_MENU = array_merge($this->pObj->MOD_MENU, $this->modMenu());
193
    }
194
195
    /**
196
     * Additions to the function menu array
197
     *
198
     * @return array Menu array
199
     */
200
    public function modMenu(): array
201
    {
202
        return [
203
            'depth' => [
204
                0 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
205
                1 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
206
                2 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
207
                3 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
208
                4 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
209
                99 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
210
            ],
211
            'crawlaction' => [
212
                'start' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.start'),
213
                'log' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.log'),
214
                'multiprocess' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.multiprocess'),
215
            ],
216
            'log_resultLog' => '',
217
            'log_feVars' => '',
218
            'processListMode' => '',
219
            'log_display' => [
220
                'all' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.all'),
221
                'pending' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.pending'),
222
                'finished' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.finished'),
223
            ],
224
            'itemsPerPage' => [
225
                '5' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.itemsPerPage.5'),
226
                '10' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.itemsPerPage.10'),
227
                '50' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.itemsPerPage.50'),
228
                '0' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.itemsPerPage.0'),
229
            ],
230
        ];
231
    }
232
233
    public function main(): string
234
    {
235
        if (empty($this->pObj->MOD_SETTINGS['processListMode'])) {
236
            $this->pObj->MOD_SETTINGS['processListMode'] = 'simple';
237
        }
238
        $this->view->assign('currentPageId', $this->id);
239
240
        $selectedAction = (string) $this->pObj->MOD_SETTINGS['crawlaction'] ?? 'start';
241
242
        // Type function menu:
243
        $actionDropdown = BackendUtility::getFuncMenu(
244
            $this->id,
245
            'SET[crawlaction]',
246
            $selectedAction,
247
            $this->pObj->MOD_MENU['crawlaction']
248
        );
249
250
        $theOutput = '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('title')) . '</h2>' . $actionDropdown;
251
252
        // Branch based on type:
253
        switch ($selectedAction) {
254
            case 'log':
255
                $quiPart = GeneralUtility::_GP('qid_details') ? '&qid_details=' . (int) GeneralUtility::_GP('qid_details') : '';
256
                $setId = (int) GeneralUtility::_GP('setID');
257
258
                // Additional menus for the log type:
259
                $theOutput .= $this->getDepthDropDownHtml();
260
                $theOutput .= $this->showLogAction($setId, $quiPart);
261
                break;
262
            case 'multiprocess':
263
                $theOutput .= $this->processOverviewAction();
264
                break;
265
            case 'start':
266
            default:
267
                $theOutput .= $this->showCrawlerInformationAction();
268
                break;
269
        }
270
271
        return $theOutput;
272
    }
273
274
    /*******************************
275
     *
276
     * Generate URLs for crawling:
277
     *
278
     ******************************/
279
280
    /**
281
     * Show a list of URLs to be crawled for each page
282
     */
283
    protected function showCrawlerInformationAction(): string
284
    {
285
        $this->view->setTemplate('ShowCrawlerInformation');
286
        if (empty($this->id)) {
287
            $this->isErrorDetected = true;
288
            MessageUtility::addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noPageSelected'));
289
        } else {
290
            $crawlerParameter = GeneralUtility::_GP('_crawl');
291
            $downloadParameter = GeneralUtility::_GP('_download');
292
293
            $this->duplicateTrack = [];
294
            $this->submitCrawlUrls = isset($crawlerParameter);
295
            $this->downloadCrawlUrls = isset($downloadParameter);
296
            $this->makeCrawlerProcessableChecks();
297
298
            switch ((string) GeneralUtility::_GP('tstamp')) {
299
                case 'midnight':
300
                    $this->scheduledTime = mktime(0, 0, 0);
301
                    break;
302
                case '04:00':
303
                    $this->scheduledTime = mktime(0, 0, 0) + 4 * 3600;
304
                    break;
305
                case 'now':
306
                default:
307
                    $this->scheduledTime = time();
308
                    break;
309
            }
310
311
            $this->incomingConfigurationSelection = GeneralUtility::_GP('configurationSelection');
312
            $this->incomingConfigurationSelection = is_array($this->incomingConfigurationSelection) ? $this->incomingConfigurationSelection : [];
313
314
            $this->crawlerController = GeneralUtility::makeInstance(CrawlerController::class);
315
            $this->crawlerController->setAccessMode('gui');
316
            $this->crawlerController->setID = GeneralUtility::md5int(microtime());
317
318
            $code = '';
319
            $noConfigurationSelected = empty($this->incomingConfigurationSelection)
320
                || (count($this->incomingConfigurationSelection) === 1 && empty($this->incomingConfigurationSelection[0]));
321
            if ($noConfigurationSelected) {
322
                MessageUtility::addWarningMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noConfigSelected'));
323
            } else {
324
                if ($this->submitCrawlUrls) {
325
                    $reason = new Reason();
326
                    $reason->setReason(Reason::REASON_GUI_SUBMIT);
327
                    $reason->setDetailText('The user ' . $GLOBALS['BE_USER']->user['username'] . ' added pages to the crawler queue manually');
328
329
                    $signalPayload = ['reason' => $reason];
330
                    SignalSlotUtility::emitSignal(
331
                        self::class,
332
                        SignalSlotUtility::SIGNAL_INVOKE_QUEUE_CHANGE,
333
                        $signalPayload
334
                    );
335
                }
336
337
                $code = $this->crawlerController->getPageTreeAndUrls(
338
                    $this->id,
339
                    $this->pObj->MOD_SETTINGS['depth'],
340
                    $this->scheduledTime,
341
                    $this->reqMinute,
342
                    $this->submitCrawlUrls,
343
                    $this->downloadCrawlUrls,
344
                    [], // Do not filter any processing instructions
345
                    $this->incomingConfigurationSelection
346
                );
347
            }
348
349
            $this->downloadUrls = $this->crawlerController->downloadUrls;
350
            $this->duplicateTrack = $this->crawlerController->duplicateTrack;
351
352
            $this->view->assign('noConfigurationSelected', $noConfigurationSelected);
353
            $this->view->assign('submitCrawlUrls', $this->submitCrawlUrls);
354
            $this->view->assign('amountOfUrls', count(array_keys($this->duplicateTrack)));
355
            $this->view->assign('selectors', $this->generateConfigurationSelectors());
356
            $this->view->assign('code', $code);
357
            $this->view->assign('displayActions', 0);
358
359
            // Download Urls to crawl:
360
            if ($this->downloadCrawlUrls) {
361
                // Creating output header:
362
                header('Content-Type: application/octet-stream');
363
                header('Content-Disposition: attachment; filename=CrawlerUrls.txt');
364
365
                // Printing the content of the CSV lines:
366
                echo implode(chr(13) . chr(10), $this->downloadUrls);
367
                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...
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
368
            }
369
        }
370
        return $this->view->render();
371
    }
372
373
    /**
374
     * Generates the configuration selectors for compiling URLs:
375
     */
376
    protected function generateConfigurationSelectors(): array
377
    {
378
        $selectors = [];
379
        $selectors['depth'] = $this->selectorBox(
380
            [
381
                0 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
382
                1 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
383
                2 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
384
                3 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
385
                4 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
386
                99 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
387
            ],
388
            'SET[depth]',
389
            $this->pObj->MOD_SETTINGS['depth'],
390
            false
391
        );
392
393
        // Configurations
394
        $availableConfigurations = $this->crawlerController->getConfigurationsForBranch((int) $this->id, (int) $this->pObj->MOD_SETTINGS['depth'] ?: 0);
395
        $selectors['configurations'] = $this->selectorBox(
396
            empty($availableConfigurations) ? [] : array_combine($availableConfigurations, $availableConfigurations),
0 ignored issues
show
Bug introduced by
It seems like empty($availableConfigur...vailableConfigurations) can also be of type false; however, parameter $optArray of AOE\Crawler\Backend\BackendModule::selectorBox() 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

396
            /** @scrutinizer ignore-type */ empty($availableConfigurations) ? [] : array_combine($availableConfigurations, $availableConfigurations),
Loading history...
397
            'configurationSelection',
398
            $this->incomingConfigurationSelection,
399
            true
400
        );
401
402
        // Scheduled time:
403
        $selectors['scheduled'] = $this->selectorBox(
404
            [
405
                'now' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.time.now'),
406
                'midnight' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.time.midnight'),
407
                '04:00' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.time.4am'),
408
            ],
409
            'tstamp',
410
            GeneralUtility::_POST('tstamp'),
411
            false
412
        );
413
414
        return $selectors;
415
    }
416
417
    /*******************************
418
     *
419
     * Shows log of indexed URLs
420
     *
421
     ******************************/
422
423
    /**
424
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
425
     */
426
    protected function showLogAction(int $setId, string $quiPath): string
427
    {
428
        $this->view->setTemplate('ShowLog');
429
        if (empty($this->id)) {
430
            $this->isErrorDetected = true;
431
            MessageUtility::addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noPageSelected'));
432
        } else {
433
            $this->crawlerController = GeneralUtility::makeInstance(CrawlerController::class);
434
            $this->crawlerController->setAccessMode('gui');
435
            $this->crawlerController->setID = GeneralUtility::md5int(microtime());
436
437
            $csvExport = GeneralUtility::_POST('_csv');
438
            $this->CSVExport = isset($csvExport);
439
440
            // Read URL:
441
            if (GeneralUtility::_GP('qid_read')) {
442
                $this->crawlerController->readUrl((int) GeneralUtility::_GP('qid_read'), true);
443
            }
444
445
            // Look for set ID sent - if it is, we will display contents of that set:
446
            $showSetId = (int) GeneralUtility::_GP('setID');
447
448
            $queueId = GeneralUtility::_GP('qid_details');
449
            $this->view->assign('queueId', $queueId);
450
            $this->view->assign('setId', $showSetId);
451
            // Show details:
452
            if ($queueId) {
453
                // Get entry record:
454
                $q_entry = $this->queryBuilder
455
                    ->from('tx_crawler_queue')
456
                    ->select('*')
457
                    ->where(
458
                        $this->queryBuilder->expr()->eq('qid', $this->queryBuilder->createNamedParameter($queueId))
459
                    )
460
                    ->execute()
461
                    ->fetch();
462
463
                // Explode values
464
                $q_entry['parameters'] = unserialize($q_entry['parameters']);
465
                $q_entry['result_data'] = unserialize($q_entry['result_data']);
466
                $resStatus = $this->getResStatus($q_entry['result_data']);
467
                if (is_array($q_entry['result_data'])) {
468
                    $q_entry['result_data']['content'] = unserialize($q_entry['result_data']['content']);
469
                    if (! $this->pObj->MOD_SETTINGS['log_resultLog']) {
470
                        unset($q_entry['result_data']['content']['log']);
471
                    }
472
                }
473
474
                $this->view->assign('queueStatus', $resStatus);
475
                $this->view->assign('queueDetails', DebugUtility::viewArray($q_entry));
476
            } else {
477
                // Show list
478
                // Drawing tree:
479
                $tree = GeneralUtility::makeInstance(PageTreeView::class);
480
                $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
481
                $tree->init('AND ' . $perms_clause);
482
483
                // Set root row:
484
                $pageinfo = BackendUtility::readPageAccess(
485
                    $this->id,
486
                    $perms_clause
487
                );
488
                $HTML = $this->iconFactory->getIconForRecord('pages', $pageinfo, Icon::SIZE_SMALL)->render();
489
                $tree->tree[] = [
490
                    'row' => $pageinfo,
491
                    'HTML' => $HTML,
492
                ];
493
494
                // Get branch beneath:
495
                if ($this->pObj->MOD_SETTINGS['depth']) {
496
                    $tree->getTree($this->id, $this->pObj->MOD_SETTINGS['depth']);
497
                }
498
499
                // If Flush button is pressed, flush tables instead of selecting entries:
500
                if (GeneralUtility::_POST('_flush')) {
501
                    $doFlush = true;
502
                    $doFullFlush = false;
503
                } elseif (GeneralUtility::_POST('_flush_all')) {
504
                    $doFlush = true;
505
                    $doFullFlush = true;
506
                } else {
507
                    $doFlush = false;
508
                    $doFullFlush = false;
509
                }
510
                $itemsPerPage = (int) $this->pObj->MOD_SETTINGS['itemsPerPage'];
511
                // Traverse page tree:
512
                $code = '';
513
                $count = 0;
514
                foreach ($tree->tree as $data) {
515
                    // Get result:
516
                    $logEntriesOfPage = $this->crawlerController->getLogEntriesForPageId(
517
                        (int) $data['row']['uid'],
518
                        $this->pObj->MOD_SETTINGS['log_display'],
519
                        $doFlush,
520
                        $doFullFlush,
521
                        $itemsPerPage
522
                    );
523
524
                    $code .= $this->drawLog_addRows(
525
                        $logEntriesOfPage,
526
                        $data['HTML'] . BackendUtility::getRecordTitle('pages', $data['row'], true)
527
                    );
528
                    if (++$count === 1000) {
529
                        break;
530
                    }
531
                }
532
                $this->view->assign('code', $code);
533
            }
534
535
            if ($this->CSVExport) {
536
                $this->outputCsvFile();
537
            }
538
        }
539
        $this->view->assign('showResultLog', (bool) $this->pObj->MOD_SETTINGS['log_resultLog']);
540
        $this->view->assign('showFeVars', (bool) $this->pObj->MOD_SETTINGS['log_feVars']);
541
        $this->view->assign('displayActions', 1);
542
        $this->view->assign('displayLogFilterHtml', $this->getDisplayLogFilterHtml($setId));
543
        $this->view->assign('itemPerPageHtml', $this->getItemsPerPageDropDownHtml());
544
        $this->view->assign('showResultLogHtml', $this->getShowResultLogCheckBoxHtml($setId, $quiPath));
545
        $this->view->assign('showFeVarsHtml', $this->getShowFeVarsCheckBoxHtml($setId, $quiPath));
546
        return $this->view->render();
547
    }
548
549
    /**
550
     * Outputs the CSV file and sets the correct headers
551
     */
552
    protected function outputCsvFile(): void
553
    {
554
        if (! count($this->CSVaccu)) {
555
            MessageUtility::addWarningMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:message.canNotExportEmptyQueueToCsvText'));
556
            return;
557
        }
558
        $csvLines = [];
559
560
        // Field names:
561
        reset($this->CSVaccu);
562
        $fieldNames = array_keys(current($this->CSVaccu));
563
        $csvLines[] = CsvUtility::csvValues($fieldNames);
564
565
        // Data:
566
        foreach ($this->CSVaccu as $row) {
567
            $csvLines[] = CsvUtility::csvValues($row);
568
        }
569
570
        // Creating output header:
571
        header('Content-Type: application/octet-stream');
572
        header('Content-Disposition: attachment; filename=CrawlerLog.csv');
573
574
        // Printing the content of the CSV lines:
575
        echo implode(chr(13) . chr(10), $csvLines);
576
        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...
577
    }
578
579
    /**
580
     * Create the rows for display of the page tree
581
     * For each page a number of rows are shown displaying GET variable configuration
582
     *
583
     * @param array $logEntriesOfPage Log items of one page
584
     * @param string $titleString Title string
585
     * @return string HTML <tr> content (one or more)
586
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
587
     */
588
    protected function drawLog_addRows(array $logEntriesOfPage, string $titleString): string
589
    {
590
        $colSpan = 9
591
            + ($this->pObj->MOD_SETTINGS['log_resultLog'] ? -1 : 0)
592
            + ($this->pObj->MOD_SETTINGS['log_feVars'] ? 3 : 0);
593
594
        if (! empty($logEntriesOfPage)) {
595
            $setId = (int) GeneralUtility::_GP('setID');
596
            $refreshIcon = $this->iconFactory->getIcon('actions-system-refresh', Icon::SIZE_SMALL);
597
            // Traverse parameter combinations:
598
            $c = 0;
599
            $content = '';
600
            foreach ($logEntriesOfPage as $vv) {
601
                // Title column:
602
                if (! $c) {
603
                    $titleClm = '<td rowspan="' . count($logEntriesOfPage) . '">' . $titleString . '</td>';
604
                } else {
605
                    $titleClm = '';
606
                }
607
608
                // Result:
609
                $resLog = $this->getResultLog($vv);
610
611
                $resultData = $vv['result_data'] ? unserialize($vv['result_data']) : [];
612
                $resStatus = $this->getResStatus($resultData);
613
614
                // Compile row:
615
                $parameters = unserialize($vv['parameters']);
616
617
                // Put data into array:
618
                $rowData = [];
619
                if ($this->pObj->MOD_SETTINGS['log_resultLog']) {
620
                    $rowData['result_log'] = $resLog;
621
                } else {
622
                    $rowData['scheduled'] = ($vv['scheduled'] > 0) ? BackendUtility::datetime($vv['scheduled']) : ' ' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.immediate');
623
                    $rowData['exec_time'] = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-';
624
                }
625
                $rowData['result_status'] = GeneralUtility::fixed_lgd_cs($resStatus, 50);
626
                $url = htmlspecialchars($parameters['url'] ?? $parameters['alturl']);
627
                $rowData['url'] = '<a href="' . $url . '" target="_newWIndow">' . $url . '</a>';
628
                $rowData['feUserGroupList'] = $parameters['feUserGroupList'] ?: '';
629
                $rowData['procInstructions'] = is_array($parameters['procInstructions']) ? implode('; ', $parameters['procInstructions']) : '';
630
                $rowData['set_id'] = (string) $vv['set_id'];
631
632
                if ($this->pObj->MOD_SETTINGS['log_feVars']) {
633
                    $resFeVars = $this->getResFeVars($resultData ?: []);
634
                    $rowData['tsfe_id'] = $resFeVars['id'] ?: '';
635
                    $rowData['tsfe_gr_list'] = $resFeVars['gr_list'] ?: '';
636
                    $rowData['tsfe_no_cache'] = $resFeVars['no_cache'] ?: '';
637
                }
638
639
                // Put rows together:
640
                $content .= '
641
                    <tr>
642
                        ' . $titleClm . '
643
                        <td><a href="' . $this->getInfoModuleUrl(['qid_details' => $vv['qid'], 'setID' => $setId]) . '">' . htmlspecialchars((string) $vv['qid']) . '</a></td>
644
                        <td><a href="' . $this->getInfoModuleUrl(['qid_read' => $vv['qid'], 'setID' => $setId]) . '">' . $refreshIcon . '</a></td>';
645
                foreach ($rowData as $fKey => $value) {
646
                    if ($fKey === 'url') {
647
                        $content .= '<td>' . $value . '</td>';
648
                    } else {
649
                        $content .= '<td>' . nl2br(htmlspecialchars(strval($value))) . '</td>';
650
                    }
651
                }
652
                $content .= '</tr>';
653
                $c++;
654
655
                if ($this->CSVExport) {
656
                    // Only for CSV (adding qid and scheduled/exec_time if needed):
657
                    $rowData['result_log'] = implode('// ', explode(chr(10), $resLog));
658
                    $rowData['qid'] = $vv['qid'];
659
                    $rowData['scheduled'] = BackendUtility::datetime($vv['scheduled']);
660
                    $rowData['exec_time'] = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-';
661
                    $this->CSVaccu[] = $rowData;
662
                }
663
            }
664
        } else {
665
            // Compile row:
666
            $content = '
667
                <tr>
668
                    <td>' . $titleString . '</td>
669
                    <td colspan="' . $colSpan . '"><em>' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noentries') . '</em></td>
670
                </tr>';
671
        }
672
673
        return $content;
674
    }
675
676
    /**
677
     * Find Fe vars
678
     */
679
    protected function getResFeVars(array $resultData): array
680
    {
681
        if (empty($resultData)) {
682
            return [];
683
        }
684
        $requestResult = unserialize($resultData['content']);
685
        return $requestResult['vars'] ?? [];
686
    }
687
688
    /**
689
     * Extract the log information from the current row and retrive it as formatted string.
690
     *
691
     * @param array $resultRow
692
     * @return string
693
     */
694
    protected function getResultLog($resultRow)
695
    {
696
        $content = '';
697
        if (is_array($resultRow) && array_key_exists('result_data', $resultRow)) {
698
            $requestContent = unserialize($resultRow['result_data']) ?: ['content' => ''];
699
            $requestResult = unserialize($requestContent['content']);
700
701
            if (is_array($requestResult) && array_key_exists('log', $requestResult)) {
702
                $content = implode(chr(10), $requestResult['log']);
703
            }
704
        }
705
        return $content;
706
    }
707
708
    protected function getResStatus($requestContent): string
709
    {
710
        if (empty($requestContent)) {
711
            return '-';
712
        }
713
        $requestResult = unserialize($requestContent['content']);
714
        if (is_array($requestResult)) {
715
            if (empty($requestResult['errorlog'])) {
716
                return 'OK';
717
            }
718
            return implode("\n", $requestResult['errorlog']);
719
        }
720
721
        if (is_bool($requestResult)) {
722
            return 'Error - no info, sorry!';
723
        }
724
725
        return 'Error: ' . substr(preg_replace('/\s+/', ' ', strip_tags($requestResult)), 0, 10000) . '...';
726
    }
727
728
    /**
729
     * This method is used to show an overview about the active an the finished crawling processes
730
     *
731
     * @return string
732
     */
733
    protected function processOverviewAction()
734
    {
735
        $this->view->setTemplate('ProcessOverview');
736
        $this->runRefreshHooks();
737
        $this->makeCrawlerProcessableChecks();
738
739
        try {
740
            $this->handleProcessOverviewActions();
741
        } catch (\Throwable $e) {
742
            $this->isErrorDetected = true;
743
            MessageUtility::addErrorMessage($e->getMessage());
744
        }
745
746
        $processRepository = new ProcessRepository();
747
        $queueRepository = new QueueRepository();
748
749
        $mode = $this->pObj->MOD_SETTINGS['processListMode'];
750
        if ($mode === 'simple') {
751
            $allProcesses = $processRepository->findAllActive();
752
        } else {
753
            $allProcesses = $processRepository->findAll();
754
        }
755
        $isCrawlerEnabled = ! $this->findCrawler()->getDisabled() && ! $this->isErrorDetected;
756
        $currentActiveProcesses = $processRepository->countActive();
757
        $maxActiveProcesses = MathUtility::forceIntegerInRange($this->extensionSettings['processLimit'], 1, 99, 1);
758
        $this->view->assignMultiple([
759
            'pageId' => (int) $this->id,
760
            'refreshLink' => $this->getRefreshLink(),
761
            'addLink' => $this->getAddLink($currentActiveProcesses, $maxActiveProcesses, $isCrawlerEnabled),
762
            'modeLink' => $this->getModeLink($mode),
763
            'enableDisableToggle' => $this->getEnableDisableLink($isCrawlerEnabled),
764
            'processCollection' => $allProcesses,
765
            'cliPath' => $this->processManager->getCrawlerCliPath(),
766
            'isCrawlerEnabled' => $isCrawlerEnabled,
767
            'totalUnprocessedItemCount' => $queueRepository->countAllPendingItems(),
768
            'assignedUnprocessedItemCount' => $queueRepository->countAllAssignedPendingItems(),
769
            'activeProcessCount' => $currentActiveProcesses,
770
            'maxActiveProcessCount' => $maxActiveProcesses,
771
            'mode' => $mode,
772
            'displayActions' => 0,
773
        ]);
774
775
        return $this->view->render();
776
    }
777
778
    /**
779
     * Returns a tag for the refresh icon
780
     *
781
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
782
     */
783
    protected function getRefreshLink(): string
784
    {
785
        return $this->getLinkButton(
786
            'actions-refresh',
787
            $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.refresh'),
788
            $this->getInfoModuleUrl(['SET[\'crawleraction\']' => 'crawleraction', 'id' => $this->id])
789
        );
790
    }
791
792
    /**
793
     * Returns a link for the panel to enable or disable the crawler
794
     *
795
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
796
     */
797
    protected function getEnableDisableLink(bool $isCrawlerEnabled): string
798
    {
799
        if ($isCrawlerEnabled) {
800
            return $this->getLinkButton(
801
                'tx-crawler-stop',
802
                $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.disablecrawling'),
803
                $this->getInfoModuleUrl(['action' => 'stopCrawling'])
804
            );
805
        }
806
        return $this->getLinkButton(
807
            'tx-crawler-start',
808
            $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.enablecrawling'),
809
            $this->getInfoModuleUrl(['action' => 'resumeCrawling'])
810
        );
811
    }
812
813
    /**
814
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
815
     */
816
    protected function getModeLink(string $mode): string
817
    {
818
        if ($mode === 'detail') {
819
            return $this->getLinkButton(
820
                'actions-document-view',
821
                $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.show.running'),
822
                $this->getInfoModuleUrl(['SET[\'processListMode\']' => 'simple'])
823
            );
824
        } elseif ($mode === 'simple') {
825
            return $this->getLinkButton(
826
                'actions-document-view',
827
                $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.show.all'),
828
                $this->getInfoModuleUrl(['SET[\'processListMode\']' => 'detail'])
829
            );
830
        }
831
        return '';
832
    }
833
834
    /**
835
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
836
     */
837
    protected function getAddLink(int $currentActiveProcesses, int $maxActiveProcesses, bool $isCrawlerEnabled): string
838
    {
839
        if (! $isCrawlerEnabled) {
840
            return '';
841
        }
842
        if ($currentActiveProcesses >= $maxActiveProcesses) {
843
            return '';
844
        }
845
846
        return $this->getLinkButton(
847
            'actions-add',
848
            $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.process.add'),
849
            $this->getInfoModuleUrl(['action' => 'addProcess'])
850
        );
851
    }
852
853
    /**
854
     * Verify that the crawler is executable.
855
     */
856
    protected function makeCrawlerProcessableChecks(): void
857
    {
858
        if (! $this->isPhpForkAvailable()) {
859
            $this->isErrorDetected = true;
860
            MessageUtility::addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:message.noPhpForkAvailable'));
861
        }
862
863
        $exitCode = 0;
864
        $out = [];
865
        CommandUtility::exec(
866
            PhpBinaryUtility::getPhpBinary() . ' -v',
867
            $out,
868
            $exitCode
869
        );
870
        if ($exitCode > 0) {
871
            $this->isErrorDetected = true;
872
            MessageUtility::addErrorMessage(sprintf($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:message.phpBinaryNotFound'), htmlspecialchars($this->extensionSettings['phpPath'])));
873
        }
874
    }
875
876
    /**
877
     * Indicate that the required PHP method "popen" is
878
     * available in the system.
879
     */
880
    protected function isPhpForkAvailable(): bool
881
    {
882
        return function_exists('popen');
883
    }
884
885
    /**
886
     * Method to handle incomming actions of the process overview
887
     *
888
     * @throws ProcessException
889
     */
890
    protected function handleProcessOverviewActions(): void
891
    {
892
        $crawler = $this->findCrawler();
893
894
        switch (GeneralUtility::_GP('action')) {
895
            case 'stopCrawling':
896
                //set the cli status to disable (all processes will be terminated)
897
                $crawler->setDisabled(true);
898
                break;
899
            case 'addProcess':
900
                if ($this->processManager->startProcess() === false) {
0 ignored issues
show
introduced by
The condition $this->processManager->startProcess() === false is always false.
Loading history...
901
                    throw new ProcessException($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.newprocesserror'));
0 ignored issues
show
Bug introduced by
The type AOE\Crawler\Backend\ProcessException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
902
                }
903
                MessageUtility::addNoticeMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.newprocess'));
904
                break;
905
            case 'resumeCrawling':
906
            default:
907
                //set the cli status to end (all processes will be terminated)
908
                $crawler->setDisabled(false);
909
                break;
910
        }
911
    }
912
913
    /**
914
     * Returns the singleton instance of the crawler.
915
     */
916
    protected function findCrawler(): CrawlerController
917
    {
918
        if (! $this->crawlerController instanceof CrawlerController) {
0 ignored issues
show
introduced by
$this->crawlerController is always a sub-type of AOE\Crawler\Controller\CrawlerController.
Loading history...
919
            $this->crawlerController = GeneralUtility::makeInstance(CrawlerController::class);
920
        }
921
        return $this->crawlerController;
922
    }
923
924
    /*****************************
925
     *
926
     * General Helper Functions
927
     *
928
     *****************************/
929
930
    /**
931
     * Create selector box
932
     *
933
     * @param array $optArray Options key(value) => label pairs
934
     * @param string $name Selector box name
935
     * @param string|array $value Selector box value (array for multiple...)
936
     * @param boolean $multiple If set, will draw multiple box.
937
     *
938
     * @return string HTML select element
939
     */
940
    protected function selectorBox($optArray, $name, $value, bool $multiple): string
941
    {
942
        if (! is_string($value) || ! is_array($value)) {
0 ignored issues
show
introduced by
The condition is_array($value) is always false.
Loading history...
943
            $value = '';
944
        }
945
946
        $options = [];
947
        foreach ($optArray as $key => $val) {
948
            $selected = (! $multiple && ! strcmp($value, (string) $key)) || ($multiple && in_array($key, (array) $value, true));
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $selected = (! $multiple..., (array)$value, true)), Probably Intended Meaning: $selected = ! $multiple ..., (array)$value, true))
Loading history...
949
            $options[] = '
950
                <option value="' . $key . '" ' . ($selected ? ' selected="selected"' : '') . '>' . htmlspecialchars($val) . '</option>';
951
        }
952
953
        return '<select class="form-control" name="' . htmlspecialchars($name . ($multiple ? '[]' : '')) . '"' . ($multiple ? ' multiple' : '') . '>' . implode('', $options) . '</select>';
954
    }
955
956
    /**
957
     * Activate hooks
958
     */
959
    protected function runRefreshHooks(): void
960
    {
961
        $crawlerLib = GeneralUtility::makeInstance(CrawlerController::class);
962
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['crawler']['refresh_hooks'] ?? [] as $objRef) {
963
            /** @var CrawlerHookInterface $hookObj */
964
            $hookObj = GeneralUtility::makeInstance($objRef);
965
            if (is_object($hookObj)) {
966
                $hookObj->crawler_init($crawlerLib);
967
            }
968
        }
969
    }
970
971
    protected function initializeView(): void
972
    {
973
        $view = GeneralUtility::makeInstance(StandaloneView::class);
974
        $view->setLayoutRootPaths(['EXT:crawler/Resources/Private/Layouts']);
975
        $view->setPartialRootPaths(['EXT:crawler/Resources/Private/Partials']);
976
        $view->setTemplateRootPaths(['EXT:crawler/Resources/Private/Templates/Backend']);
977
        $view->getRequest()->setControllerExtensionName('Crawler');
978
        $this->view = $view;
979
    }
980
981
    protected function getLanguageService(): LanguageService
982
    {
983
        return $GLOBALS['LANG'];
984
    }
985
986
    protected function getLinkButton(string $iconIdentifier, string $title, UriInterface $href): string
987
    {
988
        $moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
989
        $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
990
        return (string) $buttonBar->makeLinkButton()
991
            ->setHref((string) $href)
992
            ->setIcon($this->iconFactory->getIcon($iconIdentifier, Icon::SIZE_SMALL))
993
            ->setTitle($title)
994
            ->setShowLabelText(true);
995
    }
996
997
    /**
998
     * Returns the URL to the current module, including $_GET['id'].
999
     *
1000
     * @param array $uriParameters optional parameters to add to the URL
1001
     *
1002
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
1003
     */
1004
    protected function getInfoModuleUrl(array $uriParameters = []): Uri
1005
    {
1006
        if (GeneralUtility::_GP('id')) {
1007
            $uriParameters = array_merge($uriParameters, [
1008
                'id' => GeneralUtility::_GP('id'),
1009
            ]);
1010
        }
1011
        /** @var UriBuilder $uriBuilder */
1012
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1013
        return $uriBuilder->buildUriFromRoute('web_info', $uriParameters);
1014
    }
1015
1016
    private function getDepthDropDownHtml(): string
1017
    {
1018
        return BackendUtility::getFuncMenu(
1019
            $this->id,
1020
            'SET[depth]',
1021
            $this->pObj->MOD_SETTINGS['depth'],
1022
            $this->pObj->MOD_MENU['depth']
1023
        );
1024
    }
1025
1026
    private function getDisplayLogFilterHtml(int $setId): string
1027
    {
1028
        return $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.display') . ': ' . BackendUtility::getFuncMenu(
1029
                $this->id,
1030
                'SET[log_display]',
1031
                $this->pObj->MOD_SETTINGS['log_display'],
1032
                $this->pObj->MOD_MENU['log_display'],
1033
                'index.php',
1034
                '&setID=' . $setId
1035
            );
1036
    }
1037
1038
    private function getShowResultLogCheckBoxHtml(int $setId, string $quiPart): string
1039
    {
1040
        return BackendUtility::getFuncCheck(
1041
                $this->id,
1042
                'SET[log_resultLog]',
1043
                $this->pObj->MOD_SETTINGS['log_resultLog'],
1044
                'index.php',
1045
                '&setID=' . $setId . $quiPart
1046
            ) . '&nbsp;' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.showresultlog');
1047
    }
1048
1049
    private function getShowFeVarsCheckBoxHtml(int $setId, string $quiPart): string
1050
    {
1051
        return BackendUtility::getFuncCheck(
1052
                $this->id,
1053
                'SET[log_feVars]',
1054
                $this->pObj->MOD_SETTINGS['log_feVars'],
1055
                'index.php',
1056
                '&setID=' . $setId . $quiPart
1057
            ) . '&nbsp;' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.showfevars');
1058
    }
1059
1060
    private function getItemsPerPageDropDownHtml(): string
1061
    {
1062
        return $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.itemsPerPage') . ': ' .
1063
            BackendUtility::getFuncMenu(
1064
                $this->id,
1065
                'SET[itemsPerPage]',
1066
                $this->pObj->MOD_SETTINGS['itemsPerPage'],
1067
                $this->pObj->MOD_MENU['itemsPerPage']
1068
            );
1069
    }
1070
}
1071