Completed
Push — typo3v9 ( 43e1a1...658720 )
by Tomas Norre
06:14
created

BackendModule::drawLog_printTableHeader()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 0
dl 0
loc 24
rs 9.536
c 0
b 0
f 0
ccs 0
cts 23
cp 0
crap 12

1 Method

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