Passed
Push — issue/533 ( 0e5770 )
by Tomas Norre
07:01
created

BackendModule::getItemsPerPageDropDownHtml()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 0
dl 0
loc 8
ccs 0
cts 8
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\Event\EventDispatcher;
37
use AOE\Crawler\Hooks\CrawlerHookInterface;
38
use AOE\Crawler\Service\ProcessService;
39
use AOE\Crawler\Utility\MessageUtility;
40
use AOE\Crawler\Utility\PhpBinaryUtility;
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 'start':
255
                $theOutput .= $this->showCrawlerInformationAction();
256
                break;
257
            case 'log':
258
                $quiPart = GeneralUtility::_GP('qid_details') ? '&qid_details=' . (int) GeneralUtility::_GP('qid_details') : '';
259
                $setId = (int) GeneralUtility::_GP('setID');
260
261
                // Additional menus for the log type:
262
                $theOutput .= $this->getDepthDropDownHtml();
263
                $theOutput .= $this->showLogAction($setId, $quiPart);
264
                break;
265
            case 'multiprocess':
266
                $theOutput .= $this->processOverviewAction();
267
                break;
268
        }
269
270
        return $theOutput;
271
    }
272
273
    /*******************************
274
     *
275
     * Generate URLs for crawling:
276
     *
277
     ******************************/
278
279
    /**
280
     * Show a list of URLs to be crawled for each page
281
     */
282
    protected function showCrawlerInformationAction(): string
283
    {
284
        $this->view->setTemplate('ShowCrawlerInformation');
285
        if (empty($this->id)) {
286
            $this->isErrorDetected = true;
287
            MessageUtility::addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noPageSelected'));
288
        } else {
289
            $crawlerParameter = GeneralUtility::_GP('_crawl');
290
            $downloadParameter = GeneralUtility::_GP('_download');
291
292
            $this->duplicateTrack = [];
293
            $this->submitCrawlUrls = isset($crawlerParameter);
294
            $this->downloadCrawlUrls = isset($downloadParameter);
295
            $this->makeCrawlerProcessableChecks();
296
297
            switch ((string) GeneralUtility::_GP('tstamp')) {
298
                case 'midnight':
299
                    $this->scheduledTime = mktime(0, 0, 0);
300
                    break;
301
                case '04:00':
302
                    $this->scheduledTime = mktime(0, 0, 0) + 4 * 3600;
303
                    break;
304
                case 'now':
305
                default:
306
                    $this->scheduledTime = time();
307
                    break;
308
            }
309
310
            $this->incomingConfigurationSelection = GeneralUtility::_GP('configurationSelection');
311
            $this->incomingConfigurationSelection = is_array($this->incomingConfigurationSelection) ? $this->incomingConfigurationSelection : [];
312
313
            $this->crawlerController = GeneralUtility::makeInstance(CrawlerController::class);
314
            $this->crawlerController->setAccessMode('gui');
315
            $this->crawlerController->setID = GeneralUtility::md5int(microtime());
316
317
            $code = '';
318
            $noConfigurationSelected = empty($this->incomingConfigurationSelection)
319
                || (count($this->incomingConfigurationSelection) === 1 && empty($this->incomingConfigurationSelection[0]));
320
            if ($noConfigurationSelected) {
321
                MessageUtility::addWarningMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noConfigSelected'));
322
            } else {
323
                if ($this->submitCrawlUrls) {
324
                    $reason = new Reason();
325
                    $reason->setReason(Reason::REASON_GUI_SUBMIT);
326
                    $reason->setDetailText('The user ' . $GLOBALS['BE_USER']->user['username'] . ' added pages to the crawler queue manually');
327
328
                    EventDispatcher::getInstance()->post(
329
                        'invokeQueueChange',
330
                        strval($this->findCrawler()->setID),
331
                        ['reason' => $reason]
332
                    );
333
                }
334
335
                $code = $this->crawlerController->getPageTreeAndUrls(
336
                    $this->id,
337
                    $this->pObj->MOD_SETTINGS['depth'],
338
                    $this->scheduledTime,
339
                    $this->reqMinute,
340
                    $this->submitCrawlUrls,
341
                    $this->downloadCrawlUrls,
342
                    [], // Do not filter any processing instructions
343
                    $this->incomingConfigurationSelection
344
                );
345
            }
346
347
            $this->downloadUrls = $this->crawlerController->downloadUrls;
348
            $this->duplicateTrack = $this->crawlerController->duplicateTrack;
349
350
            $this->view->assign('noConfigurationSelected', $noConfigurationSelected);
351
            $this->view->assign('submitCrawlUrls', $this->submitCrawlUrls);
352
            $this->view->assign('amountOfUrls', count(array_keys($this->duplicateTrack)));
353
            $this->view->assign('selectors', $this->generateConfigurationSelectors());
354
            $this->view->assign('code', $code);
355
            $this->view->assign('displayActions', 0);
356
357
            // Download Urls to crawl:
358
            if ($this->downloadCrawlUrls) {
359
                // Creating output header:
360
                header('Content-Type: application/octet-stream');
361
                header('Content-Disposition: attachment; filename=CrawlerUrls.txt');
362
363
                // Printing the content of the CSV lines:
364
                echo implode(chr(13) . chr(10), $this->downloadUrls);
365
                exit;
0 ignored issues
show
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...
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...
366
            }
367
        }
368
        return $this->view->render();
369
    }
370
371
    /**
372
     * Generates the configuration selectors for compiling URLs:
373
     */
374
    protected function generateConfigurationSelectors(): array
375
    {
376
        $selectors = [];
377
        $selectors['depth'] = $this->selectorBox(
378
            [
379
                0 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
380
                1 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
381
                2 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
382
                3 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
383
                4 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
384
                99 => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
385
            ],
386
            'SET[depth]',
387
            $this->pObj->MOD_SETTINGS['depth'],
388
            false
389
        );
390
391
        // Configurations
392
        $availableConfigurations = $this->crawlerController->getConfigurationsForBranch((int) $this->id, (int) $this->pObj->MOD_SETTINGS['depth'] ?: 0);
393
        $selectors['configurations'] = $this->selectorBox(
394
            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

394
            /** @scrutinizer ignore-type */ empty($availableConfigurations) ? [] : array_combine($availableConfigurations, $availableConfigurations),
Loading history...
395
            'configurationSelection',
396
            $this->incomingConfigurationSelection,
397
            true
398
        );
399
400
        // Scheduled time:
401
        $selectors['scheduled'] = $this->selectorBox(
402
            [
403
                'now' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.time.now'),
404
                'midnight' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.time.midnight'),
405
                '04:00' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.time.4am'),
406
            ],
407
            'tstamp',
408
            GeneralUtility::_POST('tstamp'),
409
            false
410
        );
411
412
        return $selectors;
413
    }
414
415
    /*******************************
416
     *
417
     * Shows log of indexed URLs
418
     *
419
     ******************************/
420
421
    /**
422
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
423
     */
424
    protected function showLogAction(int $setId, string $quiPath): string
425
    {
426
        $this->view->setTemplate('ShowLog');
427
        if (empty($this->id)) {
428
            $this->isErrorDetected = true;
429
            MessageUtility::addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noPageSelected'));
430
        } else {
431
            $this->crawlerController = GeneralUtility::makeInstance(CrawlerController::class);
432
            $this->crawlerController->setAccessMode('gui');
433
            $this->crawlerController->setID = GeneralUtility::md5int(microtime());
434
435
            $csvExport = GeneralUtility::_POST('_csv');
436
            $this->CSVExport = isset($csvExport);
437
438
            // Read URL:
439
            if (GeneralUtility::_GP('qid_read')) {
440
                $this->crawlerController->readUrl((int) GeneralUtility::_GP('qid_read'), true);
441
            }
442
443
            // Look for set ID sent - if it is, we will display contents of that set:
444
            $showSetId = (int) GeneralUtility::_GP('setID');
445
446
            $queueId = GeneralUtility::_GP('qid_details');
447
            $this->view->assign('queueId', $queueId);
448
            $this->view->assign('setId', $showSetId);
449
            // Show details:
450
            if ($queueId) {
451
                // Get entry record:
452
                $q_entry = $this->queryBuilder
453
                    ->from('tx_crawler_queue')
454
                    ->select('*')
455
                    ->where(
456
                        $this->queryBuilder->expr()->eq('qid', $this->queryBuilder->createNamedParameter($queueId))
457
                    )
458
                    ->execute()
459
                    ->fetch();
460
461
                // Explode values
462
                $q_entry['parameters'] = unserialize($q_entry['parameters']);
463
                $q_entry['result_data'] = unserialize($q_entry['result_data']);
464
                $resStatus = $this->getResStatus($q_entry['result_data']);
465
                if (is_array($q_entry['result_data'])) {
466
                    $q_entry['result_data']['content'] = unserialize($q_entry['result_data']['content']);
467
                    if (! $this->pObj->MOD_SETTINGS['log_resultLog']) {
468
                        unset($q_entry['result_data']['content']['log']);
469
                    }
470
                }
471
472
                $this->view->assign('queueStatus', $resStatus);
473
                $this->view->assign('queueDetails', DebugUtility::viewArray($q_entry));
474
            } else {
475
                // Show list
476
                // Drawing tree:
477
                $tree = GeneralUtility::makeInstance(PageTreeView::class);
478
                $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
479
                $tree->init('AND ' . $perms_clause);
480
481
                // Set root row:
482
                $pageinfo = BackendUtility::readPageAccess(
483
                    $this->id,
484
                    $perms_clause
485
                );
486
                $HTML = $this->iconFactory->getIconForRecord('pages', $pageinfo, Icon::SIZE_SMALL)->render();
487
                $tree->tree[] = [
488
                    'row' => $pageinfo,
489
                    'HTML' => $HTML,
490
                ];
491
492
                // Get branch beneath:
493
                if ($this->pObj->MOD_SETTINGS['depth']) {
494
                    $tree->getTree($this->id, $this->pObj->MOD_SETTINGS['depth']);
495
                }
496
497
                // If Flush button is pressed, flush tables instead of selecting entries:
498
                if (GeneralUtility::_POST('_flush')) {
499
                    $doFlush = true;
500
                    $doFullFlush = false;
501
                } elseif (GeneralUtility::_POST('_flush_all')) {
502
                    $doFlush = true;
503
                    $doFullFlush = true;
504
                } else {
505
                    $doFlush = false;
506
                    $doFullFlush = false;
507
                }
508
                $itemsPerPage = (int) $this->pObj->MOD_SETTINGS['itemsPerPage'];
509
                // Traverse page tree:
510
                $code = '';
511
                $count = 0;
512
                foreach ($tree->tree as $data) {
513
                    // Get result:
514
                    $logEntriesOfPage = $this->crawlerController->getLogEntriesForPageId(
515
                        (int) $data['row']['uid'],
516
                        $this->pObj->MOD_SETTINGS['log_display'],
517
                        $doFlush,
518
                        $doFullFlush,
519
                        $itemsPerPage
520
                    );
521
522
                    $code .= $this->drawLog_addRows(
523
                        $logEntriesOfPage,
524
                        $data['HTML'] . BackendUtility::getRecordTitle('pages', $data['row'], true)
525
                    );
526
                    if (++$count === 1000) {
527
                        break;
528
                    }
529
                }
530
                $this->view->assign('code', $code);
531
            }
532
533
            if ($this->CSVExport) {
534
                $this->outputCsvFile();
535
            }
536
        }
537
        $this->view->assign('showResultLog', (bool) $this->pObj->MOD_SETTINGS['log_resultLog']);
538
        $this->view->assign('showFeVars', (bool) $this->pObj->MOD_SETTINGS['log_feVars']);
539
        $this->view->assign('displayActions', 1);
540
        $this->view->assign('displayLogFilterHtml', $this->getDisplayLogFilterHtml($setId));
541
        $this->view->assign('itemPerPageHtml', $this->getItemsPerPageDropDownHtml());
542
        $this->view->assign('showResultLogHtml', $this->getShowResultLogCheckBoxHtml($setId, $quiPath));
543
        $this->view->assign('showFeVarsHtml', $this->getShowFeVarsCheckBoxHtml($setId, $quiPath));
544
        return $this->view->render();
545
    }
546
547
    /**
548
     * Outputs the CSV file and sets the correct headers
549
     */
550
    protected function outputCsvFile(): void
551
    {
552
        if (! count($this->CSVaccu)) {
553
            MessageUtility::addWarningMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:message.canNotExportEmptyQueueToCsvText'));
554
            return;
555
        }
556
        $csvLines = [];
557
558
        // Field names:
559
        reset($this->CSVaccu);
560
        $fieldNames = array_keys(current($this->CSVaccu));
561
        $csvLines[] = CsvUtility::csvValues($fieldNames);
562
563
        // Data:
564
        foreach ($this->CSVaccu as $row) {
565
            $csvLines[] = CsvUtility::csvValues($row);
566
        }
567
568
        // Creating output header:
569
        header('Content-Type: application/octet-stream');
570
        header('Content-Disposition: attachment; filename=CrawlerLog.csv');
571
572
        // Printing the content of the CSV lines:
573
        echo implode(chr(13) . chr(10), $csvLines);
574
        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...
575
    }
576
577
    /**
578
     * Create the rows for display of the page tree
579
     * For each page a number of rows are shown displaying GET variable configuration
580
     *
581
     * @param array $logEntriesOfPage Log items of one page
582
     * @param string $titleString Title string
583
     * @return string HTML <tr> content (one or more)
584
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
585
     */
586
    protected function drawLog_addRows(array $logEntriesOfPage, string $titleString): string
587
    {
588
        $colSpan = 9
589
            + ($this->pObj->MOD_SETTINGS['log_resultLog'] ? -1 : 0)
590
            + ($this->pObj->MOD_SETTINGS['log_feVars'] ? 3 : 0);
591
592
        if (! empty($logEntriesOfPage)) {
593
            $setId = (int) GeneralUtility::_GP('setID');
594
            $refreshIcon = $this->iconFactory->getIcon('actions-system-refresh', Icon::SIZE_SMALL);
595
            // Traverse parameter combinations:
596
            $c = 0;
597
            $content = '';
598
            foreach ($logEntriesOfPage as $vv) {
599
                // Title column:
600
                if (! $c) {
601
                    $titleClm = '<td rowspan="' . count($logEntriesOfPage) . '">' . $titleString . '</td>';
602
                } else {
603
                    $titleClm = '';
604
                }
605
606
                // Result:
607
                $resLog = $this->getResultLog($vv);
608
609
                $resultData = $vv['result_data'] ? unserialize($vv['result_data']) : [];
610
                $resStatus = $this->getResStatus($resultData);
611
612
                // Compile row:
613
                $parameters = unserialize($vv['parameters']);
614
615
                // Put data into array:
616
                $rowData = [];
617
                if ($this->pObj->MOD_SETTINGS['log_resultLog']) {
618
                    $rowData['result_log'] = $resLog;
619
                } else {
620
                    $rowData['scheduled'] = ($vv['scheduled'] > 0) ? BackendUtility::datetime($vv['scheduled']) : ' ' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.immediate');
621
                    $rowData['exec_time'] = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-';
622
                }
623
                $rowData['result_status'] = GeneralUtility::fixed_lgd_cs($resStatus, 50);
624
                $url = htmlspecialchars($parameters['url'] ?? $parameters['alturl']);
625
                $rowData['url'] = '<a href="' . $url . '" target="_newWIndow">' . $url . '</a>';
626
                $rowData['feUserGroupList'] = $parameters['feUserGroupList'] ?: '';
627
                $rowData['procInstructions'] = is_array($parameters['procInstructions']) ? implode('; ', $parameters['procInstructions']) : '';
628
                $rowData['set_id'] = (string) $vv['set_id'];
629
630
                if ($this->pObj->MOD_SETTINGS['log_feVars']) {
631
                    $resFeVars = $this->getResFeVars($resultData ?: []);
632
                    $rowData['tsfe_id'] = $resFeVars['id'] ?: '';
633
                    $rowData['tsfe_gr_list'] = $resFeVars['gr_list'] ?: '';
634
                    $rowData['tsfe_no_cache'] = $resFeVars['no_cache'] ?: '';
635
                }
636
637
                // Put rows together:
638
                $content .= '
639
                    <tr>
640
                        ' . $titleClm . '
641
                        <td><a href="' . $this->getInfoModuleUrl(['qid_details' => $vv['qid'], 'setID' => $setId]) . '">' . htmlspecialchars((string) $vv['qid']) . '</a></td>
642
                        <td><a href="' . $this->getInfoModuleUrl(['qid_read' => $vv['qid'], 'setID' => $setId]) . '">' . $refreshIcon . '</a></td>';
643
                foreach ($rowData as $fKey => $value) {
644
                    if ($fKey === 'url') {
645
                        $content .= '<td>' . $value . '</td>';
646
                    } else {
647
                        $content .= '<td>' . nl2br(htmlspecialchars(strval($value))) . '</td>';
648
                    }
649
                }
650
                $content .= '</tr>';
651
                $c++;
652
653
                if ($this->CSVExport) {
654
                    // Only for CSV (adding qid and scheduled/exec_time if needed):
655
                    $rowData['result_log'] = implode('// ', explode(chr(10), $resLog));
656
                    $rowData['qid'] = $vv['qid'];
657
                    $rowData['scheduled'] = BackendUtility::datetime($vv['scheduled']);
658
                    $rowData['exec_time'] = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-';
659
                    $this->CSVaccu[] = $rowData;
660
                }
661
            }
662
        } else {
663
            // Compile row:
664
            $content = '
665
                <tr>
666
                    <td>' . $titleString . '</td>
667
                    <td colspan="' . $colSpan . '"><em>' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.noentries') . '</em></td>
668
                </tr>';
669
        }
670
671
        return $content;
672
    }
673
674
    /**
675
     * Find Fe vars
676
     */
677
    protected function getResFeVars(array $resultData): array
678
    {
679
        if (empty($resultData)) {
680
            return [];
681
        }
682
        $requestResult = unserialize($resultData['content']);
683
        return $requestResult['vars'] ?? [];
684
    }
685
686
    /**
687
     * Extract the log information from the current row and retrive it as formatted string.
688
     *
689
     * @param array $resultRow
690
     * @return string
691
     */
692
    protected function getResultLog($resultRow)
693
    {
694
        $content = '';
695
        if (is_array($resultRow) && array_key_exists('result_data', $resultRow)) {
696
            $requestContent = unserialize($resultRow['result_data']) ?: ['content' => ''];
697
            $requestResult = unserialize($requestContent['content']);
698
699
            if (is_array($requestResult) && array_key_exists('log', $requestResult)) {
700
                $content = implode(chr(10), $requestResult['log']);
701
            }
702
        }
703
        return $content;
704
    }
705
706
    protected function getResStatus($requestContent): string
707
    {
708
        if (empty($requestContent)) {
709
            return '-';
710
        }
711
        $requestResult = unserialize($requestContent['content']);
712
        if (is_array($requestResult)) {
713
            if (empty($requestResult['errorlog'])) {
714
                return 'OK';
715
            }
716
            return implode("\n", $requestResult['errorlog']);
717
        }
718
719
        if (is_bool($requestResult)) {
720
            return 'Error - no info, sorry!';
721
        }
722
723
        return 'Error: ' . substr(preg_replace('/\s+/', ' ', strip_tags($requestResult)), 0, 10000) . '...';
724
    }
725
726
    /**
727
     * This method is used to show an overview about the active an the finished crawling processes
728
     *
729
     * @return string
730
     */
731
    protected function processOverviewAction()
732
    {
733
        $this->view->setTemplate('ProcessOverview');
734
        $this->runRefreshHooks();
735
        $this->makeCrawlerProcessableChecks();
736
737
        try {
738
            $this->handleProcessOverviewActions();
739
        } catch (\Throwable $e) {
740
            $this->isErrorDetected = true;
741
            MessageUtility::addErrorMessage($e->getMessage());
742
        }
743
744
        $processRepository = new ProcessRepository();
745
        $queueRepository = new QueueRepository();
746
747
        $mode = $this->pObj->MOD_SETTINGS['processListMode'];
748
        if ($mode === 'simple') {
749
            $allProcesses = $processRepository->findAllActive();
750
        } else {
751
            $allProcesses = $processRepository->findAll();
752
        }
753
        $isCrawlerEnabled = ! $this->findCrawler()->getDisabled() && ! $this->isErrorDetected;
754
        $currentActiveProcesses = $processRepository->countActive();
755
        $maxActiveProcesses = MathUtility::forceIntegerInRange($this->extensionSettings['processLimit'], 1, 99, 1);
756
        $this->view->assignMultiple([
757
            'pageId' => (int) $this->id,
758
            'refreshLink' => $this->getRefreshLink(),
759
            'addLink' => $this->getAddLink($currentActiveProcesses, $maxActiveProcesses, $isCrawlerEnabled),
760
            'modeLink' => $this->getModeLink($mode),
761
            'enableDisableToggle' => $this->getEnableDisableLink($isCrawlerEnabled),
762
            'processCollection' => $allProcesses,
763
            'cliPath' => $this->processManager->getCrawlerCliPath(),
764
            'isCrawlerEnabled' => $isCrawlerEnabled,
765
            'totalUnprocessedItemCount' => $queueRepository->countAllPendingItems(),
766
            'assignedUnprocessedItemCount' => $queueRepository->countAllAssignedPendingItems(),
767
            'activeProcessCount' => $currentActiveProcesses,
768
            'maxActiveProcessCount' => $maxActiveProcesses,
769
            'mode' => $mode,
770
            'displayActions' => 0,
771
        ]);
772
773
        return $this->view->render();
774
    }
775
776
    /**
777
     * Returns a tag for the refresh icon
778
     *
779
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
780
     */
781
    protected function getRefreshLink(): string
782
    {
783
        return $this->getLinkButton(
784
            'actions-refresh',
785
            $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.refresh'),
786
            $this->getInfoModuleUrl(['SET[\'crawleraction\']' => 'crawleraction', 'id' => $this->id])
787
        );
788
    }
789
790
    /**
791
     * Returns a link for the panel to enable or disable the crawler
792
     *
793
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
794
     */
795
    protected function getEnableDisableLink(bool $isCrawlerEnabled): string
796
    {
797
        if ($isCrawlerEnabled) {
798
            // TODO: Icon Should be bigger + Perhaps better icon
799
            return $this->getLinkButton(
800
                'tx-crawler-stop',
801
                $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.disablecrawling'),
802
                $this->getInfoModuleUrl(['action' => 'stopCrawling'])
803
            );
804
        }
805
        // TODO: Icon Should be bigger
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 \Exception
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 'resumeCrawling':
900
                //set the cli status to end (all processes will be terminated)
901
                $crawler->setDisabled(false);
902
                break;
903
            case 'addProcess':
904
                if ($this->processManager->startProcess() === false) {
0 ignored issues
show
introduced by
The condition $this->processManager->startProcess() === false is always false.
Loading history...
905
                    throw new \Exception($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.newprocesserror'));
906
                }
907
                MessageUtility::addNoticeMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.newprocess'));
908
                break;
909
        }
910
    }
911
912
    /**
913
     * Returns the singleton instance of the crawler.
914
     */
915
    protected function findCrawler(): CrawlerController
916
    {
917
        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...
918
            $this->crawlerController = GeneralUtility::makeInstance(CrawlerController::class);
919
        }
920
        return $this->crawlerController;
921
    }
922
923
    /*****************************
924
     *
925
     * General Helper Functions
926
     *
927
     *****************************/
928
929
    /**
930
     * Create selector box
931
     *
932
     * @param array $optArray Options key(value) => label pairs
933
     * @param string $name Selector box name
934
     * @param string|array $value Selector box value (array for multiple...)
935
     * @param boolean $multiple If set, will draw multiple box.
936
     *
937
     * @return string HTML select element
938
     */
939
    protected function selectorBox($optArray, $name, $value, bool $multiple): string
940
    {
941
        if (! is_string($value) || ! is_array($value)) {
0 ignored issues
show
introduced by
The condition is_array($value) is always false.
Loading history...
942
            $value = '';
943
        }
944
945
        $options = [];
946
        foreach ($optArray as $key => $val) {
947
            $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...
948
            $options[] = '
949
                <option value="' . $key . '" ' . ($selected ? ' selected="selected"' : '') . '>' . htmlspecialchars($val) . '</option>';
950
        }
951
952
        return '<select class="form-control" name="' . htmlspecialchars($name . ($multiple ? '[]' : '')) . '"' . ($multiple ? ' multiple' : '') . '>' . implode('', $options) . '</select>';
953
    }
954
955
    /**
956
     * Activate hooks
957
     */
958
    protected function runRefreshHooks(): void
959
    {
960
        $crawlerLib = GeneralUtility::makeInstance(CrawlerController::class);
961
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['crawler']['refresh_hooks'] ?? [] as $objRef) {
962
            /** @var CrawlerHookInterface $hookObj */
963
            $hookObj = GeneralUtility::makeInstance($objRef);
964
            if (is_object($hookObj)) {
965
                $hookObj->crawler_init($crawlerLib);
966
            }
967
        }
968
    }
969
970
    protected function initializeView(): void
971
    {
972
        $view = GeneralUtility::makeInstance(StandaloneView::class);
973
        $view->setLayoutRootPaths(['EXT:crawler/Resources/Private/Layouts']);
974
        $view->setPartialRootPaths(['EXT:crawler/Resources/Private/Partials']);
975
        $view->setTemplateRootPaths(['EXT:crawler/Resources/Private/Templates/Backend']);
976
        $view->getRequest()->setControllerExtensionName('Crawler');
977
        $this->view = $view;
978
    }
979
980
    protected function getLanguageService(): LanguageService
981
    {
982
        return $GLOBALS['LANG'];
983
    }
984
985
    protected function getLinkButton(string $iconIdentifier, string $title, UriInterface $href): string
986
    {
987
        $moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
988
        $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
989
        return (string) $buttonBar->makeLinkButton()
990
            ->setHref((string) $href)
991
            ->setIcon($this->iconFactory->getIcon($iconIdentifier, Icon::SIZE_SMALL))
992
            ->setTitle($title)
993
            ->setShowLabelText(true);
994
    }
995
996
    /**
997
     * Returns the URL to the current module, including $_GET['id'].
998
     *
999
     * @param array $uriParameters optional parameters to add to the URL
1000
     *
1001
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
1002
     */
1003
    protected function getInfoModuleUrl(array $uriParameters = []): Uri
1004
    {
1005
        if (GeneralUtility::_GP('id')) {
1006
            $uriParameters = array_merge($uriParameters, [
1007
                'id' => GeneralUtility::_GP('id'),
1008
            ]);
1009
        }
1010
        /** @var UriBuilder $uriBuilder */
1011
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1012
        return $uriBuilder->buildUriFromRoute('web_info', $uriParameters);
1013
    }
1014
1015
    private function getDepthDropDownHtml(): string
1016
    {
1017
        return BackendUtility::getFuncMenu(
1018
            $this->id,
1019
            'SET[depth]',
1020
            $this->pObj->MOD_SETTINGS['depth'],
1021
            $this->pObj->MOD_MENU['depth']
1022
        );
1023
    }
1024
1025
    private function getDisplayLogFilterHtml(int $setId): string
1026
    {
1027
        return $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.display') . ': ' . BackendUtility::getFuncMenu(
1028
                $this->id,
1029
                'SET[log_display]',
1030
                $this->pObj->MOD_SETTINGS['log_display'],
1031
                $this->pObj->MOD_MENU['log_display'],
1032
                'index.php',
1033
                '&setID=' . $setId
1034
            );
1035
    }
1036
1037
    private function getShowResultLogCheckBoxHtml(int $setId, string $quiPart): string
1038
    {
1039
        return BackendUtility::getFuncCheck(
1040
                $this->id,
1041
                'SET[log_resultLog]',
1042
                $this->pObj->MOD_SETTINGS['log_resultLog'],
1043
                'index.php',
1044
                '&setID=' . $setId . $quiPart
1045
            ) . '&nbsp;' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.showresultlog');
1046
    }
1047
1048
    private function getShowFeVarsCheckBoxHtml(int $setId, string $quiPart): string
1049
    {
1050
        return BackendUtility::getFuncCheck(
1051
                $this->id,
1052
                'SET[log_feVars]',
1053
                $this->pObj->MOD_SETTINGS['log_feVars'],
1054
                'index.php',
1055
                '&setID=' . $setId . $quiPart
1056
            ) . '&nbsp;' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.showfevars');
1057
    }
1058
1059
    private function getItemsPerPageDropDownHtml(): string
1060
    {
1061
        return $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xlf:labels.itemsPerPage') . ': ' .
1062
            BackendUtility::getFuncMenu(
1063
                $this->id,
1064
                'SET[itemsPerPage]',
1065
                $this->pObj->MOD_SETTINGS['itemsPerPage'],
1066
                $this->pObj->MOD_MENU['itemsPerPage']
1067
            );
1068
    }
1069
}
1070