Completed
Push — typo3v9 ( 73c725...c8617d )
by Tomas Norre
05:22
created

BackendModule::getInfoModuleUrl()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 11
ccs 0
cts 10
cp 0
crap 6
rs 9.9
c 0
b 0
f 0
1
<?php
2
namespace AOE\Crawler\Backend;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2018 AOE GmbH <[email protected]>
8
 *
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 3 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use AOE\Crawler\Configuration\ExtensionConfigurationProvider;
29
use AOE\Crawler\Controller\CrawlerController;
30
use AOE\Crawler\Domain\Model\Reason;
31
use AOE\Crawler\Domain\Repository\ProcessRepository;
32
use AOE\Crawler\Domain\Repository\QueueRepository;
33
use AOE\Crawler\Event\EventDispatcher;
34
use AOE\Crawler\Service\ProcessService;
35
use Psr\Http\Message\UriInterface;
36
use TYPO3\CMS\Backend\Routing\UriBuilder;
37
use TYPO3\CMS\Backend\Template\ModuleTemplate;
38
use TYPO3\CMS\Backend\Tree\View\PageTreeView;
39
use TYPO3\CMS\Backend\Utility\BackendUtility;
40
use TYPO3\CMS\Core\Database\ConnectionPool;
41
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
42
use TYPO3\CMS\Core\Http\Uri;
43
use TYPO3\CMS\Core\Imaging\Icon;
44
use TYPO3\CMS\Core\Imaging\IconFactory;
45
use TYPO3\CMS\Core\Localization\LanguageService;
46
use TYPO3\CMS\Core\Messaging\FlashMessage;
47
use TYPO3\CMS\Core\Messaging\FlashMessageService;
48
use TYPO3\CMS\Core\Utility\CommandUtility;
49
use TYPO3\CMS\Core\Utility\CsvUtility;
50
use TYPO3\CMS\Core\Utility\DebugUtility;
51
use TYPO3\CMS\Core\Utility\GeneralUtility;
52
use TYPO3\CMS\Core\Utility\MathUtility;
53
use TYPO3\CMS\Extbase\Object\ObjectManager;
54
use TYPO3\CMS\Fluid\View\StandaloneView;
55
use TYPO3\CMS\Info\Controller\InfoModuleController;
56
57
/**
58
 * Function for Info module, containing three main actions:
59
 * - List of all queued items
60
 * - Log functionality
61
 * - Process overview
62
 */
63
class BackendModule
64
{
65
    /**
66
     * @var InfoModuleController Contains a reference to the parent calling object
67
     */
68
    protected $pObj;
69
70
    /**
71
     * The current page ID
72
     * @var int
73
     */
74
    protected $id;
75
76
    // Internal, dynamic:
77
    protected $duplicateTrack = [];
78
    protected $submitCrawlUrls = false;
79
    protected $downloadCrawlUrls = false;
80
81
    protected $scheduledTime = 0;
82
    protected $reqMinute = 1000;
83
84
    /**
85
     * @var array holds the selection of configuration from the configuration selector box
86
     */
87
    protected $incomingConfigurationSelection = [];
88
89
    /**
90
     * @var CrawlerController
91
     */
92
    protected $crawlerController;
93
94
    /**
95
     * @var array
96
     */
97
    protected $CSVaccu = [];
98
99
    /**
100
     * If true the user requested a CSV export of the queue
101
     *
102
     * @var boolean
103
     */
104
    protected $CSVExport = false;
105
106
    /**
107
     * @var array
108
     */
109
    protected $downloadUrls = [];
110
111
    /**
112
     * Holds the configuration from ext_conf_template loaded by getExtensionConfiguration()
113
     *
114
     * @var array
115
     */
116
    protected $extensionSettings = [];
117
118
    /**
119
     * Indicate that an flash message with an error is present.
120
     *
121
     * @var boolean
122
     */
123
    protected $isErrorDetected = false;
124
125
    /**
126
     * @var ProcessService
127
     */
128
    protected $processManager;
129
130
    /**
131
     * @var QueryBuilder
132
     */
133
    protected $queryBuilder;
134
135
    /**
136
     * @var QueueRepository
137
     */
138
    protected $queueRepository;
139
140
    /**
141
     * @var StandaloneView
142
     */
143
    protected $view;
144
145
    /**
146
     * @var IconFactory
147
     */
148
    protected $iconFactory;
149
150
    public function __construct()
151
    {
152
        $objectManger = GeneralUtility::makeInstance(ObjectManager::class);
153
        $this->processManager = $objectManger->get(ProcessService::class);
154
        $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_crawler_queue');
155
        $this->queueRepository = $objectManger->get(QueueRepository::class);
156
        $this->initializeView();
157
        $this->extensionSettings = GeneralUtility::makeInstance(ExtensionConfigurationProvider::class)->getExtensionConfiguration();
158
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
159
    }
160
161
    /**
162
     * Called by the InfoModuleController
163
     * @param InfoModuleController $pObj
164
     */
165
    public function init(InfoModuleController $pObj): void
166
    {
167
        $this->pObj = $pObj;
168
        $this->id = (int)GeneralUtility::_GP('id');
169
        // Setting MOD_MENU items as we need them for logging:
170
        $this->pObj->MOD_MENU = array_merge($this->pObj->MOD_MENU, $this->modMenu());
171
    }
172
173
    /**
174
     * Additions to the function menu array
175
     *
176
     * @return array Menu array
177
     */
178
    public function modMenu(): array
179
    {
180
        return [
181
            'depth' => [
182
                0 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
183
                1 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
184
                2 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
185
                3 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
186
                4 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
187
                99 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
188
            ],
189
            'crawlaction' => [
190
                'start' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.start'),
191
                'log' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.log'),
192
                'multiprocess' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.multiprocess')
193
            ],
194
            'log_resultLog' => '',
195
            'log_feVars' => '',
196
            'processListMode' => '',
197
            'log_display' => [
198
                'all' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.all'),
199
                'pending' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.pending'),
200
                'finished' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.finished')
201
            ],
202
            'itemsPerPage' => [
203
                '5' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.itemsPerPage.5'),
204
                '10' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.itemsPerPage.10'),
205
                '50' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.itemsPerPage.50'),
206
                '0' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.itemsPerPage.0')
207
            ]
208
        ];
209
    }
210
211
    public function main(): string
212
    {
213
        if (empty($this->pObj->MOD_SETTINGS['processListMode'])) {
214
            $this->pObj->MOD_SETTINGS['processListMode'] = 'simple';
215
        }
216
        $this->view->assign('currentPageId', $this->id);
217
218
        $selectedAction = (string)$this->pObj->MOD_SETTINGS['crawlaction'] ?? 'start';
219
220
        // Type function menu:
221
        $actionDropdown = BackendUtility::getFuncMenu(
222
            $this->id,
223
            'SET[crawlaction]',
224
            $selectedAction,
225
            $this->pObj->MOD_MENU['crawlaction']
226
        );
227
228
        $theOutput = '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('title')) . '</h2>' . $actionDropdown;
229
230
        // Branch based on type:
231
        switch ($selectedAction) {
232
            case 'start':
233
                $theOutput .= $this->showCrawlerInformationAction();
234
                break;
235
            case 'log':
236
237
                // Additional menus for the log type:
238
                $theOutput .= BackendUtility::getFuncMenu(
239
                    $this->id,
240
                    'SET[depth]',
241
                    $this->pObj->MOD_SETTINGS['depth'],
242
                    $this->pObj->MOD_MENU['depth']
243
                );
244
245
                $quiPart = GeneralUtility::_GP('qid_details') ? '&qid_details=' . (int)GeneralUtility::_GP('qid_details') : '';
246
                $setId = (int)GeneralUtility::_GP('setID');
247
248
                $theOutput .= '<br><br>' .
249
                    $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.display') . ': ' . BackendUtility::getFuncMenu(
250
                        $this->id,
251
                        'SET[log_display]',
252
                        $this->pObj->MOD_SETTINGS['log_display'],
253
                        $this->pObj->MOD_MENU['log_display'],
254
                        'index.php',
255
                        '&setID=' . $setId
256
                    ) . ' ' .
257
                    $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.showresultlog') . ': ' . BackendUtility::getFuncCheck(
258
                        $this->id,
259
                        'SET[log_resultLog]',
260
                        $this->pObj->MOD_SETTINGS['log_resultLog'],
261
                        'index.php',
262
                        '&setID=' . $setId . $quiPart
263
                    ) . ' ' .
264
                    $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.showfevars') . ': ' . BackendUtility::getFuncCheck(
265
                        $this->id,
266
                        'SET[log_feVars]',
267
                        $this->pObj->MOD_SETTINGS['log_feVars'],
268
                        'index.php',
269
                        '&setID=' . $setId . $quiPart
270
                    ) . ' ' .
271
                    $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.itemsPerPage') . ': ' .
272
                    BackendUtility::getFuncMenu(
273
                        $this->id,
274
                        'SET[itemsPerPage]',
275
                        $this->pObj->MOD_SETTINGS['itemsPerPage'],
276
                        $this->pObj->MOD_MENU['itemsPerPage']
277
                    );
278
                $theOutput .= $this->showLogAction();
279
                break;
280
            case 'multiprocess':
281
                $theOutput .= $this->processOverviewAction();
282
                break;
283
        }
284
285
        return $theOutput;
286
    }
287
288
    /*******************************
289
     *
290
     * Generate URLs for crawling:
291
     *
292
     ******************************/
293
294
    /**
295
     * Show a list of URLs to be crawled for each page
296
     * @return string
297
     */
298
    protected function showCrawlerInformationAction(): string
299
    {
300
        $this->view->setTemplate('ShowCrawlerInformation');
301
        if (empty($this->id)) {
302
            $this->addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.noPageSelected'));
303
        } else {
304
            $crawlerParameter = GeneralUtility::_GP('_crawl');
305
            $downloadParameter = GeneralUtility::_GP('_download');
306
307
            $this->duplicateTrack = [];
308
            $this->submitCrawlUrls = isset($crawlerParameter);
309
            $this->downloadCrawlUrls = isset($downloadParameter);
310
            $this->makeCrawlerProcessableChecks();
311
312
            switch ((string) GeneralUtility::_GP('tstamp')) {
313
                case 'midnight':
314
                    $this->scheduledTime = mktime(0, 0, 0);
315
                    break;
316
                case '04:00':
317
                    $this->scheduledTime = mktime(0, 0, 0) + 4 * 3600;
318
                    break;
319
                case 'now':
320
                default:
321
                    $this->scheduledTime = time();
322
                    break;
323
            }
324
325
            $this->incomingConfigurationSelection = GeneralUtility::_GP('configurationSelection');
326
            $this->incomingConfigurationSelection = is_array($this->incomingConfigurationSelection) ? $this->incomingConfigurationSelection : [];
327
328
            $this->crawlerController = GeneralUtility::makeInstance(CrawlerController::class);
329
            $this->crawlerController->setAccessMode('gui');
330
            $this->crawlerController->setID = GeneralUtility::md5int(microtime());
331
332
            $code = '';
333
            $noConfigurationSelected = empty($this->incomingConfigurationSelection)
334
                || (count($this->incomingConfigurationSelection) == 1 && empty($this->incomingConfigurationSelection[0]));
335
            if ($noConfigurationSelected) {
336
                $this->addWarningMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.noConfigSelected'));
337
            } else {
338
                if ($this->submitCrawlUrls) {
339
                    $reason = new Reason();
340
                    $reason->setReason(Reason::REASON_GUI_SUBMIT);
341
                    $reason->setDetailText('The user ' . $GLOBALS['BE_USER']->user['username'] . ' added pages to the crawler queue manually');
342
343
                    EventDispatcher::getInstance()->post(
344
                        'invokeQueueChange',
345
                        $this->findCrawler()->setID,
346
                        ['reason' => $reason]
347
                    );
348
                }
349
350
                $code = $this->crawlerController->getPageTreeAndUrls(
351
                    $this->id,
352
                    $this->pObj->MOD_SETTINGS['depth'],
353
                    $this->scheduledTime,
354
                    $this->reqMinute,
355
                    $this->submitCrawlUrls,
356
                    $this->downloadCrawlUrls,
357
                    [], // Do not filter any processing instructions
358
                    $this->incomingConfigurationSelection
359
                );
360
            }
361
362
            $this->downloadUrls = $this->crawlerController->downloadUrls;
363
            $this->duplicateTrack = $this->crawlerController->duplicateTrack;
364
365
            $this->view->assign('noConfigurationSelected', $noConfigurationSelected);
366
            $this->view->assign('submitCrawlUrls', $this->submitCrawlUrls);
367
            $this->view->assign('amountOfUrls', count(array_keys($this->duplicateTrack)));
368
            $this->view->assign('selectors', $this->generateConfigurationSelectors());
369
            $this->view->assign('code', $code);
370
371
            // Download Urls to crawl:
372
            if ($this->downloadCrawlUrls) {
373
                // Creating output header:
374
                header('Content-Type: application/octet-stream');
375
                header('Content-Disposition: attachment; filename=CrawlerUrls.txt');
376
377
                // Printing the content of the CSV lines:
378
                echo implode(chr(13) . chr(10), $this->downloadUrls);
379
                exit;
380
            }
381
        }
382
        return $this->view->render();
383
    }
384
385
    /**
386
     * Generates the configuration selectors for compiling URLs:
387
     */
388
    protected function generateConfigurationSelectors(): array
389
    {
390
        $selectors = [];
391
        $selectors['depth'] = $this->selectorBox(
392
            [
393
                0 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
394
                1 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
395
                2 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
396
                3 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
397
                4 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
398
                99 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
399
            ],
400
            'SET[depth]',
401
            $this->pObj->MOD_SETTINGS['depth'],
402
            false
403
        );
404
405
        // Configurations
406
        $availableConfigurations = $this->crawlerController->getConfigurationsForBranch((int)$this->id, $this->pObj->MOD_SETTINGS['depth'] ?: 0);
407
        $selectors['configurations'] = $this->selectorBox(
408
            empty($availableConfigurations) ? [] : array_combine($availableConfigurations, $availableConfigurations),
409
            'configurationSelection',
410
            $this->incomingConfigurationSelection,
411
            true
412
        );
413
414
        // Scheduled time:
415
        $selectors['scheduled'] = $this->selectorBox(
416
            [
417
                'now' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.time.now'),
418
                'midnight' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.time.midnight'),
419
                '04:00' => $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.time.4am'),
420
            ],
421
            'tstamp',
422
            GeneralUtility::_POST('tstamp'),
423
            false
424
        );
425
426
        return $selectors;
427
    }
428
429
    /*******************************
430
     *
431
     * Shows log of indexed URLs
432
     *
433
     ******************************/
434
435
    protected function showLogAction(): string
436
    {
437
        $this->view->setTemplate('ShowLog');
438
        if (empty($this->id)) {
439
            $this->addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.noPageSelected'));
440
        } else {
441
            $this->crawlerController = GeneralUtility::makeInstance(CrawlerController::class);
442
            $this->crawlerController->setAccessMode('gui');
443
            $this->crawlerController->setID = GeneralUtility::md5int(microtime());
444
445
            $csvExport = GeneralUtility::_POST('_csv');
446
            $this->CSVExport = isset($csvExport);
447
448
            // Read URL:
449
            if (GeneralUtility::_GP('qid_read')) {
450
                $this->crawlerController->readUrl((int)GeneralUtility::_GP('qid_read'), true);
451
            }
452
453
            // Look for set ID sent - if it is, we will display contents of that set:
454
            $showSetId = (int)GeneralUtility::_GP('setID');
455
456
            $queueId = GeneralUtility::_GP('qid_details');
457
            $this->view->assign('queueId', $queueId);
458
            $this->view->assign('setId', $showSetId);
459
            // Show details:
460
            if ($queueId) {
461
                // Get entry record:
462
                $q_entry = $this->queryBuilder
463
                    ->from('tx_crawler_queue')
464
                    ->select('*')
465
                    ->where(
466
                        $this->queryBuilder->expr()->eq('qid', $this->queryBuilder->createNamedParameter($queueId))
467
                    )
468
                    ->execute()
469
                    ->fetch();
470
471
                // Explode values
472
                $q_entry['parameters'] = unserialize($q_entry['parameters']);
473
                $q_entry['result_data'] = unserialize($q_entry['result_data']);
474
                $resStatus = $this->getResStatus($q_entry['result_data']);
475
                if (is_array($q_entry['result_data'])) {
476
                    $q_entry['result_data']['content'] = unserialize($q_entry['result_data']['content']);
477
                    if (!$this->pObj->MOD_SETTINGS['log_resultLog']) {
478
                        unset($q_entry['result_data']['content']['log']);
479
                    }
480
                }
481
482
                $this->view->assign('queueStatus', $resStatus);
483
                $this->view->assign('queueDetails', DebugUtility::viewArray($q_entry));
484
            } else {
485
                // Show list
486
                // Drawing tree:
487
                $tree = GeneralUtility::makeInstance(PageTreeView::class);
488
                $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
489
                $tree->init('AND ' . $perms_clause);
490
491
                // Set root row:
492
                $pageinfo = BackendUtility::readPageAccess(
493
                    $this->id,
494
                    $perms_clause
495
                );
496
                $HTML = $this->iconFactory->getIconForRecord('pages', $pageinfo, Icon::SIZE_SMALL)->render();
497
                $tree->tree[] = [
498
                    'row' => $pageinfo,
499
                    'HTML' => $HTML
500
                ];
501
502
                // Get branch beneath:
503
                if ($this->pObj->MOD_SETTINGS['depth']) {
504
                    $tree->getTree($this->id, $this->pObj->MOD_SETTINGS['depth']);
505
                }
506
507
                // If Flush button is pressed, flush tables instead of selecting entries:
508
                if (GeneralUtility::_POST('_flush')) {
509
                    $doFlush = true;
510
                    $doFullFlush = false;
511
                } elseif (GeneralUtility::_POST('_flush_all')) {
512
                    $doFlush = true;
513
                    $doFullFlush = true;
514
                } else {
515
                    $doFlush = false;
516
                    $doFullFlush = false;
517
                }
518
                $itemsPerPage = (int)$this->pObj->MOD_SETTINGS['itemsPerPage'];
519
                // Traverse page tree:
520
                $code = '';
521
                $count = 0;
522
                foreach ($tree->tree as $data) {
523
                    // Get result:
524
                    $logEntriesOfPage = $this->crawlerController->getLogEntriesForPageId(
525
                        (int)$data['row']['uid'],
526
                        $this->pObj->MOD_SETTINGS['log_display'],
527
                        $doFlush,
528
                        $doFullFlush,
529
                        $itemsPerPage
530
                    );
531
532
                    $code .= $this->drawLog_addRows(
533
                        $logEntriesOfPage,
534
                        $data['HTML'] . BackendUtility::getRecordTitle('pages', $data['row'], true)
535
                    );
536
                    if (++$count === 1000) {
537
                        break;
538
                    }
539
                }
540
                $this->view->assign('code', $code);
541
            }
542
543
            if ($this->CSVExport) {
544
                $this->outputCsvFile();
545
            }
546
        }
547
        $this->view->assign('showResultLog', (bool)$this->pObj->MOD_SETTINGS['log_resultLog']);
548
        $this->view->assign('showFeVars', (bool)$this->pObj->MOD_SETTINGS['log_feVars']);
549
        return $this->view->render();
550
    }
551
552
    /**
553
     * Outputs the CSV file and sets the correct headers
554
     */
555
    protected function outputCsvFile(): void
556
    {
557
        if (!count($this->CSVaccu)) {
558
            $this->addWarningMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:message.canNotExportEmptyQueueToCsvText'));
559
            return;
560
        }
561
        $csvLines = [];
562
563
        // Field names:
564
        reset($this->CSVaccu);
565
        $fieldNames = array_keys(current($this->CSVaccu));
566
        $csvLines[] = CsvUtility::csvValues($fieldNames);
567
568
        // Data:
569
        foreach ($this->CSVaccu as $row) {
570
            $csvLines[] = CsvUtility::csvValues($row);
571
        }
572
573
        // Creating output header:
574
        header('Content-Type: application/octet-stream');
575
        header('Content-Disposition: attachment; filename=CrawlerLog.csv');
576
577
        // Printing the content of the CSV lines:
578
        echo implode(chr(13) . chr(10), $csvLines);
579
        exit;
580
    }
581
582
    /**
583
     * Create the rows for display of the page tree
584
     * For each page a number of rows are shown displaying GET variable configuration
585
     *
586
     * @param array $logEntriesOfPage Log items of one page
587
     * @param string $titleString Title string
588
     * @return string HTML <tr> content (one or more)
589
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
590
     */
591
    protected function drawLog_addRows(array $logEntriesOfPage, string $titleString): string
592
    {
593
        $colSpan = 9
594
                + ($this->pObj->MOD_SETTINGS['log_resultLog'] ? -1 : 0)
595
                + ($this->pObj->MOD_SETTINGS['log_feVars'] ? 3 : 0);
596
597
        if (!empty($logEntriesOfPage)) {
598
            $setId = (int)GeneralUtility::_GP('setID');
599
            $refreshIcon = $this->iconFactory->getIcon('actions-system-refresh', Icon::SIZE_SMALL);
600
            // Traverse parameter combinations:
601
            $c = 0;
602
            $content = '';
603
            foreach ($logEntriesOfPage as $kk => $vv) {
604
                // Title column:
605
                if (!$c) {
606
                    $titleClm = '<td rowspan="' . count($logEntriesOfPage) . '">' . $titleString . '</td>';
607
                } else {
608
                    $titleClm = '';
609
                }
610
611
                // Result:
612
                $resLog = $this->getResultLog($vv);
613
614
                $resultData = $vv['result_data'] ? unserialize($vv['result_data']) : [];
615
                $resStatus = $this->getResStatus($resultData);
616
617
                // Compile row:
618
                $parameters = unserialize($vv['parameters']);
619
620
                // Put data into array:
621
                $rowData = [];
622
                if ($this->pObj->MOD_SETTINGS['log_resultLog']) {
623
                    $rowData['result_log'] = $resLog;
624
                } else {
625
                    $rowData['scheduled'] = ($vv['scheduled'] > 0) ? BackendUtility::datetime($vv['scheduled']) : ' ' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.immediate');
626
                    $rowData['exec_time'] = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-';
627
                }
628
                $rowData['result_status'] = GeneralUtility::fixed_lgd_cs($resStatus, 50);
629
                $rowData['url'] = '<a href="' . htmlspecialchars($parameters['url']) . '" target="_newWIndow">' . htmlspecialchars($parameters['url']) . '</a>';
630
                $rowData['feUserGroupList'] = $parameters['feUserGroupList'];
631
                $rowData['procInstructions'] = is_array($parameters['procInstructions']) ? implode('; ', $parameters['procInstructions']) : '';
632
                $rowData['set_id'] = $vv['set_id'];
633
634
                if ($this->pObj->MOD_SETTINGS['log_feVars']) {
635
                    $resFeVars = $this->getResFeVars($resultData ?: []);
636
                    $rowData['tsfe_id'] = $resFeVars['id'];
637
                    $rowData['tsfe_gr_list'] = $resFeVars['gr_list'];
638
                    $rowData['tsfe_no_cache'] = $resFeVars['no_cache'];
639
                }
640
641
                // Put rows together:
642
                $content .= '
643
					<tr>
644
						' . $titleClm . '
645
						<td><a href="' . $this->getInfoModuleUrl(['qid_details' => $vv['qid'], 'setID' => $setId]) . '">' . htmlspecialchars($vv['qid']) . '</a></td>
646
						<td><a href="' . $this->getInfoModuleUrl(['qid_read' => $vv['qid'], 'setID' => $setId]) . '">' . $refreshIcon . '</a></td>';
647
                foreach ($rowData as $fKey => $value) {
648
                    if ($fKey === 'url') {
649
                        $content .= '<td>' . $value . '</td>';
650
                    } else {
651
                        $content .= '<td>' . nl2br(htmlspecialchars($value)) . '</td>';
652
                    }
653
                }
654
                $content .= '</tr>';
655
                $c++;
656
657
                if ($this->CSVExport) {
658
                    // Only for CSV (adding qid and scheduled/exec_time if needed):
659
                    $rowData['result_log'] = implode('// ', explode(chr(10), $resLog));
660
                    $rowData['qid'] = $vv['qid'];
661
                    $rowData['scheduled'] = BackendUtility::datetime($vv['scheduled']);
662
                    $rowData['exec_time'] = $vv['exec_time'] ? BackendUtility::datetime($vv['exec_time']) : '-';
663
                    $this->CSVaccu[] = $rowData;
664
                }
665
            }
666
        } else {
667
            // Compile row:
668
            $content = '
669
				<tr>
670
					<td>' . $titleString . '</td>
671
					<td colspan="' . $colSpan . '"><em>' . $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.noentries') . '</em></td>
672
				</tr>';
673
        }
674
675
        return $content;
676
    }
677
678
    /**
679
     * Find Fe vars
680
     *
681
     * @param array $resultData
682
     * @return array
683
     */
684
    protected function getResFeVars(array $resultData): array
685
    {
686
        if (empty($resultData)) {
687
            return [];
688
        }
689
        $requestResult = unserialize($resultData['content']);
690
        return $requestResult['vars'];
691
    }
692
693
    /**
694
     * Extract the log information from the current row and retrive it as formatted string.
695
     *
696
     * @param array $resultRow
697
     * @return string
698
     */
699
    protected function getResultLog($resultRow)
700
    {
701
        $content = '';
702
        if (is_array($resultRow) && array_key_exists('result_data', $resultRow)) {
703
            $requestContent = unserialize($resultRow['result_data']);
704
            $requestResult = unserialize($requestContent['content']);
705
706
            if (is_array($requestResult) && array_key_exists('log', $requestResult)) {
707
                $content = implode(chr(10), $requestResult['log']);
708
            }
709
        }
710
        return $content;
711
    }
712
713
    protected function getResStatus($requestContent): string
714
    {
715
        if (empty($requestContent)) {
716
            return '-';
717
        }
718
        $requestResult = unserialize($requestContent['content']);
719
        if (is_array($requestResult)) {
720
            if (empty($requestResult['errorlog'])) {
721
                return 'OK';
722
            } else {
723
                return implode("\n", $requestResult['errorlog']);
724
            }
725
        }
726
        return 'Error: ' . substr(preg_replace('/\s+/', ' ', strip_tags($requestContent['content'])), 0, 10000) . '...';
727
    }
728
729
    /**
730
     * This method is used to show an overview about the active an the finished crawling processes
731
     *
732
     * @return string
733
     */
734
    protected function processOverviewAction()
735
    {
736
        $this->view->setTemplate('ProcessOverview');
737
        $this->runRefreshHooks();
738
        $this->makeCrawlerProcessableChecks();
739
740
        try {
741
            $this->handleProcessOverviewActions();
742
        } catch (\Exception $e) {
743
            $this->addErrorMessage($e->getMessage());
744
        }
745
746
        $processRepository = new ProcessRepository();
747
        $queueRepository = new QueueRepository();
748
749
        $mode = $this->pObj->MOD_SETTINGS['processListMode'];
750
        if ($mode == 'simple') {
751
            $allProcesses = $processRepository->findAllActive();
752
        } else {
753
            $allProcesses = $processRepository->findAll();
754
        }
755
        $isCrawlerEnabled = !$this->findCrawler()->getDisabled() && !$this->isErrorDetected;
756
        $currentActiveProcesses = $processRepository->countActive();
757
        $maxActiveProcesses = MathUtility::forceIntegerInRange($this->extensionSettings['processLimit'], 1, 99, 1);
758
        $this->view->assignMultiple([
759
            'pageId' => (int)$this->id,
760
            'refreshLink' => $this->getRefreshLink(),
761
            'addLink' => $this->getAddLink($currentActiveProcesses, $maxActiveProcesses, $isCrawlerEnabled),
762
            'modeLink' => $this->getModeLink($mode),
763
            'enableDisableToggle' => $this->getEnableDisableLink($isCrawlerEnabled),
764
            'processCollection' => $allProcesses,
765
            'cliPath' => $this->processManager->getCrawlerCliPath(),
766
            'isCrawlerEnabled' => $isCrawlerEnabled,
767
            'totalUnprocessedItemCount' => $queueRepository->countAllPendingItems(),
768
            'assignedUnprocessedItemCount' => $queueRepository->countAllAssignedPendingItems(),
769
            'activeProcessCount' => $currentActiveProcesses,
770
            'maxActiveProcessCount' => $maxActiveProcesses,
771
            'mode' => $mode
772
        ]);
773
774
        return $this->view->render();
775
    }
776
777
    /**
778
     * Returns a tag for the refresh icon
779
     *
780
     * @return string
781
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
782
     */
783
    protected function getRefreshLink(): string
784
    {
785
        return $this->getLinkButton(
786
            'actions-refresh',
787
            $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.refresh'),
788
            $this->getInfoModuleUrl(['SET[\'crawleraction\']' => 'crawleraction', 'id' => $this->id])
789
        );
790
    }
791
792
    /**
793
     * Returns a link for the panel to enable or disable the crawler
794
     *
795
     * @param bool $isCrawlerEnabled
796
     * @return string
797
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
798
     */
799
    protected function getEnableDisableLink(bool $isCrawlerEnabled): string
800
    {
801
        if ($isCrawlerEnabled) {
802
            // TODO: Icon Should be bigger + Perhaps better icon
803
            return $this->getLinkButton(
804
                'tx-crawler-stop',
805
                $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.disablecrawling'),
806
                $this->getInfoModuleUrl(['action' => 'stopCrawling'])
807
            );
808
        } else {
809
            // TODO: Icon Should be bigger
810
            return $this->getLinkButton(
811
                'tx-crawler-start',
812
                $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.enablecrawling'),
813
                $this->getInfoModuleUrl(['action' => 'resumeCrawling'])
814
            );
815
        }
816
    }
817
818
    /**
819
     * @param string $mode
820
     * @return string
821
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
822
     */
823
    protected function getModeLink(string $mode): string
824
    {
825
        if ($mode === 'detail') {
826
            return $this->getLinkButton(
827
                'actions-document-view',
828
                $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.show.running'),
829
                $this->getInfoModuleUrl(['SET[\'processListMode\']' => 'simple'])
830
            );
831
        } elseif ($mode === 'simple') {
832
            return $this->getLinkButton(
833
                'actions-document-view',
834
                $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.show.all'),
835
                $this->getInfoModuleUrl(['SET[\'processListMode\']' => 'detail'])
836
            );
837
        }
838
        return '';
839
    }
840
841
    /**
842
     * @param $currentActiveProcesses
843
     * @param $maxActiveProcesses
844
     * @param $isCrawlerEnabled
845
     * @return string
846
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
847
     */
848
    protected function getAddLink($currentActiveProcesses, $maxActiveProcesses, $isCrawlerEnabled): string
849
    {
850
        if (!$isCrawlerEnabled) {
851
            return '';
852
        }
853
        if (MathUtility::convertToPositiveInteger($currentActiveProcesses) >= MathUtility::convertToPositiveInteger($maxActiveProcesses)) {
854
            return '';
855
        }
856
        return $this->getLinkButton(
857
            'actions-add',
858
            $this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.process.add'),
859
            $this->getInfoModuleUrl(['action' => 'addProcess'])
860
        );
861
    }
862
863
    /**
864
     * Verify that the crawler is executable.
865
     */
866
    protected function makeCrawlerProcessableChecks(): void
867
    {
868
        if (!$this->isPhpForkAvailable()) {
869
            $this->addErrorMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:message.noPhpForkAvailable'));
870
        }
871
872
        $exitCode = 0;
873
        $out = [];
874
        CommandUtility::exec(
875
            CommandUtility::escapeShellArgument($this->extensionSettings['phpPath'] . ' -v'),
876
            $out,
877
            $exitCode
878
        );
879
        if ($exitCode > 0) {
880
            $this->addErrorMessage(sprintf($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:message.phpBinaryNotFound'), htmlspecialchars($this->extensionSettings['phpPath'])));
881
        }
882
    }
883
884
    /**
885
     * Indicate that the required PHP method "popen" is
886
     * available in the system.
887
     */
888
    protected function isPhpForkAvailable(): bool
889
    {
890
        return function_exists('popen');
891
    }
892
893
    /**
894
     * Method to handle incomming actions of the process overview
895
     *
896
     * @throws \Exception
897
     */
898
    protected function handleProcessOverviewActions(): void
899
    {
900
        $crawler = $this->findCrawler();
901
902
        switch (GeneralUtility::_GP('action')) {
903
            case 'stopCrawling':
904
                //set the cli status to disable (all processes will be terminated)
905
                $crawler->setDisabled(true);
906
                break;
907
            case 'resumeCrawling':
908
                //set the cli status to end (all processes will be terminated)
909
                $crawler->setDisabled(false);
910
                break;
911
            case 'addProcess':
912
                if ($this->processManager->startProcess() === false) {
913
                    throw new \Exception($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.newprocesserror'));
914
                }
915
                $this->addNoticeMessage($this->getLanguageService()->sL('LLL:EXT:crawler/Resources/Private/Language/locallang.xml:labels.newprocess'));
916
                break;
917
        }
918
    }
919
920
    /**
921
     * Returns the singleton instance of the crawler.
922
     */
923
    protected function findCrawler(): CrawlerController
924
    {
925
        if (!$this->crawlerController instanceof CrawlerController) {
926
            $this->crawlerController = GeneralUtility::makeInstance(CrawlerController::class);
927
        }
928
        return $this->crawlerController;
929
    }
930
931
    /*****************************
932
     *
933
     * General Helper Functions
934
     *
935
     *****************************/
936
937
    /**
938
     * This method is used to add a message to the internal queue
939
     *
940
     * @param  string  the message itself
941
     * @param  integer message level (-1 = success (default), 0 = info, 1 = notice, 2 = warning, 3 = error)
942
     */
943
    protected function addMessage($message, $severity = FlashMessage::OK): void
944
    {
945
        $message = GeneralUtility::makeInstance(
946
            FlashMessage::class,
947
            $message,
948
            '',
949
            $severity
950
        );
951
952
        // TODO:
953
        /** @var FlashMessageService $flashMessageService */
954
        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
955
        $flashMessageService->getMessageQueueByIdentifier()->addMessage($message);
956
    }
957
958
    /**
959
     * Add notice message to the user interface.
960
     *
961
     * @param string The message
962
     */
963
    protected function addNoticeMessage(string $message): void
964
    {
965
        $this->addMessage($message, FlashMessage::NOTICE);
966
    }
967
968
    /**
969
     * Add error message to the user interface.
970
     *
971
     * @param string The message
972
     */
973
    protected function addErrorMessage(string $message): void
974
    {
975
        $this->isErrorDetected = true;
976
        $this->addMessage($message, FlashMessage::ERROR);
977
    }
978
979
    /**
980
     * Add error message to the user interface.
981
     *
982
     * @param string The message
983
     */
984
    protected function addWarningMessage(string $message): void
985
    {
986
        $this->addMessage($message, FlashMessage::WARNING);
987
    }
988
989
    /**
990
     * Create selector box
991
     *
992
     * @param	array		$optArray Options key(value) => label pairs
993
     * @param	string		$name Selector box name
994
     * @param	string|array		$value Selector box value (array for multiple...)
995
     * @param	boolean		$multiple If set, will draw multiple box.
996
     *
997
     * @return	string		HTML select element
998
     */
999
    protected function selectorBox($optArray, $name, $value, bool $multiple): string
1000
    {
1001
        $options = [];
1002
        foreach ($optArray as $key => $val) {
1003
            $selected = (!$multiple && !strcmp($value, $key)) || ($multiple && in_array($key, (array)$value));
1004
            $options[] = '
1005
				<option value="' . htmlspecialchars($key) . '"' . ($selected ? ' selected="selected"' : '') . '>' . htmlspecialchars($val) . '</option>';
1006
        }
1007
1008
        return '<select class="form-control" name="' . htmlspecialchars($name . ($multiple ? '[]' : '')) . '"' . ($multiple ? ' multiple' : '') . '>' . implode('', $options) . '</select>';
1009
    }
1010
1011
    /**
1012
     * Activate hooks
1013
     */
1014
    protected function runRefreshHooks(): void
1015
    {
1016
        $crawlerLib = GeneralUtility::makeInstance(CrawlerController::class);
1017
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['crawler']['refresh_hooks'] ?? [] as $objRef) {
1018
            $hookObj = GeneralUtility::makeInstance($objRef);
1019
            if (is_object($hookObj)) {
1020
                $hookObj->crawler_init($crawlerLib);
1021
            }
1022
        }
1023
    }
1024
1025
    protected function initializeView(): void
1026
    {
1027
        $view = GeneralUtility::makeInstance(StandaloneView::class);
1028
        $view->setLayoutRootPaths(['EXT:crawler/Resources/Private/Layouts']);
1029
        $view->setPartialRootPaths(['EXT:crawler/Resources/Private/Partials']);
1030
        $view->setTemplateRootPaths(['EXT:crawler/Resources/Private/Templates/Backend']);
1031
        $view->getRequest()->setControllerExtensionName('Crawler');
1032
        $this->view = $view;
1033
    }
1034
1035
    protected function getLanguageService(): LanguageService
1036
    {
1037
        return $GLOBALS['LANG'];
1038
    }
1039
1040
    /**
1041
     * @param string $iconIdentifier
1042
     * @param string $title
1043
     * @param UriInterface $href
1044
     * @return string
1045
     */
1046
    protected function getLinkButton(string $iconIdentifier, string $title, UriInterface $href): string
1047
    {
1048
        $moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
1049
        $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
1050
        return (string)$buttonBar->makeLinkButton()
1051
            ->setHref((string)$href)
1052
            ->setIcon($this->iconFactory->getIcon($iconIdentifier, Icon::SIZE_SMALL))
1053
            ->setTitle($title)
1054
            ->setShowLabelText(true);
1055
    }
1056
1057
    /**
1058
     * Returns the URL to the current module, including $_GET['id'].
1059
     *
1060
     * @param array $uriParameters optional parameters to add to the URL
1061
     * @return Uri
1062
     *
1063
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
1064
     */
1065
    protected function getInfoModuleUrl(array $uriParameters = []): Uri
1066
    {
1067
        if (GeneralUtility::_GP('id')) {
1068
            $uriParameters = array_merge($uriParameters, [
1069
                'id' => GeneralUtility::_GP('id')
1070
            ]);
1071
        }
1072
        /** @var UriBuilder $uriBuilder */
1073
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1074
        return $uriBuilder->buildUriFromRoute('web_info', $uriParameters);
1075
    }
1076
}
1077