Passed
Push — typo3v9 ( c45ace...8bb658 )
by Tomas Norre
05:20
created

BackendModule::getRefreshLink()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 6
ccs 0
cts 6
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AOE\Crawler\Backend;
6
7
/***************************************************************
8
 *  Copyright notice
9
 *
10
 *  (c) 2018 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
     * @var array
83
     */
84
    protected $duplicateTrack = [];
85
86
    /**
87
     * @var bool
88
     */
89
    protected $submitCrawlUrls = false;
90
91
    /**
92
     * @var bool
93
     */
94
    protected $downloadCrawlUrls = false;
95
96
    /**
97
     * @var int
98
     */
99
    protected $scheduledTime = 0;
100
101
    /**
102
     * @var int
103
     */
104
    protected $reqMinute = 1000;
105
106
    /**
107
     * @var array holds the selection of configuration from the configuration selector box
108
     */
109
    protected $incomingConfigurationSelection = [];
110
111
    /**
112
     * @var CrawlerController
113
     */
114
    protected $crawlerController;
115
116
    /**
117
     * @var array
118
     */
119
    protected $CSVaccu = [];
120
121
    /**
122
     * If true the user requested a CSV export of the queue
123
     *
124
     * @var boolean
125
     */
126
    protected $CSVExport = false;
127
128
    /**
129
     * @var array
130
     */
131
    protected $downloadUrls = [];
132
133
    /**
134
     * Holds the configuration from ext_conf_template loaded by getExtensionConfiguration()
135
     *
136
     * @var array
137
     */
138
    protected $extensionSettings = [];
139
140
    /**
141
     * Indicate that an flash message with an error is present.
142
     *
143
     * @var boolean
144
     */
145
    protected $isErrorDetected = false;
146
147
    /**
148
     * @var ProcessService
149
     */
150
    protected $processManager;
151
152
    /**
153
     * @var QueryBuilder
154
     */
155
    protected $queryBuilder;
156
157
    /**
158
     * @var QueueRepository
159
     */
160
    protected $queueRepository;
161
162
    /**
163
     * @var StandaloneView
164
     */
165
    protected $view;
166
167
    /**
168
     * @var IconFactory
169
     */
170
    protected $iconFactory;
171
172
    public function __construct()
173
    {
174
        $objectManger = GeneralUtility::makeInstance(ObjectManager::class);
175
        $this->processManager = $objectManger->get(ProcessService::class);
176
        $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_crawler_queue');
177
        $this->queueRepository = $objectManger->get(QueueRepository::class);
178
        $this->initializeView();
179
        $this->extensionSettings = GeneralUtility::makeInstance(ExtensionConfigurationProvider::class)->getExtensionConfiguration();
180
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
181
    }
182
183
    /**
184
     * Called by the InfoModuleController
185
     * @param InfoModuleController $pObj
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:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
205
                1 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
206
                2 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
207
                3 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
208
                4 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
209
                99 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
210
            ],
211
            'crawlaction' => [
212
                'start' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.start'),
213
                'log' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.log'),
214
                'multiprocess' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml: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.xml:labels.all'),
221
                'pending' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.pending'),
222
                'finished' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.finished'),
223
            ],
224
            'itemsPerPage' => [
225
                '5' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.itemsPerPage.5'),
226
                '10' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.itemsPerPage.10'),
227
                '50' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.itemsPerPage.50'),
228
                '0' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml: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
259
                // Additional menus for the log type:
260
                $theOutput .= BackendUtility::getFuncMenu(
261
                    $this->id,
262
                    'SET[depth]',
263
                    $this->pObj->MOD_SETTINGS['depth'],
264
                    $this->pObj->MOD_MENU['depth']
265
                );
266
267
                $quiPart = GeneralUtility::_GP('qid_details') ? '&qid_details=' . (int)GeneralUtility::_GP('qid_details') : '';
268
                $setId = (int)GeneralUtility::_GP('setID');
269
270
                $theOutput .= '<br><br>' .
271
                    $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.display') . ': ' . BackendUtility::getFuncMenu(
272
                        $this->id,
273
                        'SET[log_display]',
274
                        $this->pObj->MOD_SETTINGS['log_display'],
275
                        $this->pObj->MOD_MENU['log_display'],
276
                        'index.php',
277
                        '&setID=' . $setId
278
                    ) . ' ' .
279
                    $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.showresultlog') . ': ' . BackendUtility::getFuncCheck(
280
                        $this->id,
281
                        'SET[log_resultLog]',
282
                        $this->pObj->MOD_SETTINGS['log_resultLog'],
283
                        'index.php',
284
                        '&setID=' . $setId . $quiPart
285
                    ) . ' ' .
286
                    $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.showfevars') . ': ' . BackendUtility::getFuncCheck(
287
                        $this->id,
288
                        'SET[log_feVars]',
289
                        $this->pObj->MOD_SETTINGS['log_feVars'],
290
                        'index.php',
291
                        '&setID=' . $setId . $quiPart
292
                    ) . ' ' .
293
                    $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.itemsPerPage') . ': ' .
294
                    BackendUtility::getFuncMenu(
295
                        $this->id,
296
                        'SET[itemsPerPage]',
297
                        $this->pObj->MOD_SETTINGS['itemsPerPage'],
298
                        $this->pObj->MOD_MENU['itemsPerPage']
299
                    );
300
                $theOutput .= $this->showLogAction();
301
                break;
302
            case 'multiprocess':
303
                $theOutput .= $this->processOverviewAction();
304
                break;
305
        }
306
307
        return $theOutput;
308
    }
309
310
    /*******************************
311
     *
312
     * Generate URLs for crawling:
313
     *
314
     ******************************/
315
316
    /**
317
     * Show a list of URLs to be crawled for each page
318
     * @return string
319
     */
320
    protected function showCrawlerInformationAction(): string
321
    {
322
        $this->view->setTemplate('ShowCrawlerInformation');
323
        if (empty($this->id)) {
324
            $this->isErrorDetected = true;
325
            MessageUtility::addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.noPageSelected'));
326
        } else {
327
            $crawlerParameter = GeneralUtility::_GP('_crawl');
328
            $downloadParameter = GeneralUtility::_GP('_download');
329
330
            $this->duplicateTrack = [];
331
            $this->submitCrawlUrls = isset($crawlerParameter);
332
            $this->downloadCrawlUrls = isset($downloadParameter);
333
            $this->makeCrawlerProcessableChecks();
334
335
            switch ((string) GeneralUtility::_GP('tstamp')) {
336
                case 'midnight':
337
                    $this->scheduledTime = mktime(0, 0, 0);
338
                    break;
339
                case '04:00':
340
                    $this->scheduledTime = mktime(0, 0, 0) + 4 * 3600;
341
                    break;
342
                case 'now':
343
                default:
344
                    $this->scheduledTime = time();
345
                    break;
346
            }
347
348
            $this->incomingConfigurationSelection = GeneralUtility::_GP('configurationSelection');
349
            $this->incomingConfigurationSelection = is_array($this->incomingConfigurationSelection) ? $this->incomingConfigurationSelection : [];
350
351
            $this->crawlerController = GeneralUtility::makeInstance(CrawlerController::class);
352
            $this->crawlerController->setAccessMode('gui');
353
            $this->crawlerController->setID = GeneralUtility::md5int(microtime());
354
355
            $code = '';
356
            $noConfigurationSelected = empty($this->incomingConfigurationSelection)
357
                || (count($this->incomingConfigurationSelection) == 1 && empty($this->incomingConfigurationSelection[0]));
358
            if ($noConfigurationSelected) {
359
                MessageUtility::addWarningMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.noConfigSelected'));
360
            } else {
361
                if ($this->submitCrawlUrls) {
362
                    $reason = new Reason();
363
                    $reason->setReason(Reason::REASON_GUI_SUBMIT);
364
                    $reason->setDetailText('The user ' . $GLOBALS['BE_USER']->user['username'] . ' added pages to the crawler queue manually');
365
366
                    EventDispatcher::getInstance()->post(
367
                        'invokeQueueChange',
368
                        strval($this->findCrawler()->setID),
369
                        ['reason' => $reason]
370
                    );
371
                }
372
373
                $code = $this->crawlerController->getPageTreeAndUrls(
374
                    $this->id,
375
                    $this->pObj->MOD_SETTINGS['depth'],
376
                    $this->scheduledTime,
377
                    $this->reqMinute,
378
                    $this->submitCrawlUrls,
379
                    $this->downloadCrawlUrls,
380
                    [], // Do not filter any processing instructions
381
                    $this->incomingConfigurationSelection
382
                );
383
            }
384
385
            $this->downloadUrls = $this->crawlerController->downloadUrls;
386
            $this->duplicateTrack = $this->crawlerController->duplicateTrack;
387
388
            $this->view->assign('noConfigurationSelected', $noConfigurationSelected);
389
            $this->view->assign('submitCrawlUrls', $this->submitCrawlUrls);
390
            $this->view->assign('amountOfUrls', count(array_keys($this->duplicateTrack)));
391
            $this->view->assign('selectors', $this->generateConfigurationSelectors());
392
            $this->view->assign('code', $code);
393
394
            // Download Urls to crawl:
395
            if ($this->downloadCrawlUrls) {
396
                // Creating output header:
397
                header('Content-Type: application/octet-stream');
398
                header('Content-Disposition: attachment; filename=CrawlerUrls.txt');
399
400
                // Printing the content of the CSV lines:
401
                echo implode(chr(13) . chr(10), $this->downloadUrls);
402
                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...
403
            }
404
        }
405
        return $this->view->render();
406
    }
407
408
    /**
409
     * Generates the configuration selectors for compiling URLs:
410
     */
411
    protected function generateConfigurationSelectors(): array
412
    {
413
        $selectors = [];
414
        $selectors['depth'] = $this->selectorBox(
415
            [
416
                0 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
417
                1 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
418
                2 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
419
                3 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
420
                4 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
421
                99 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
422
            ],
423
            'SET[depth]',
424
            $this->pObj->MOD_SETTINGS['depth'],
425
            false
426
        );
427
428
        // Configurations
429
        $availableConfigurations = $this->crawlerController->getConfigurationsForBranch((int)$this->id, $this->pObj->MOD_SETTINGS['depth'] ?: 0);
430
        $selectors['configurations'] = $this->selectorBox(
431
            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

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