Passed
Push — master ( 6aab4a...f2ba87 )
by
unknown
17:34
created

BackendLogController::createPageDepthOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 0
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Belog\Controller;
17
18
use Psr\Http\Message\ResponseInterface;
19
use TYPO3\CMS\Backend\Utility\BackendUtility;
20
use TYPO3\CMS\Belog\Domain\Model\Constraint;
21
use TYPO3\CMS\Belog\Domain\Model\LogEntry;
22
use TYPO3\CMS\Belog\Domain\Repository\LogEntryRepository;
23
use TYPO3\CMS\Belog\Domain\Repository\WorkspaceRepository;
24
use TYPO3\CMS\Core\Messaging\AbstractMessage;
25
use TYPO3\CMS\Core\Page\PageRenderer;
26
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
27
use TYPO3\CMS\Core\Utility\GeneralUtility;
28
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
29
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
30
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
31
32
/**
33
 * Abstract class to show log entries from sys_log
34
 * @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
35
 */
36
class BackendLogController extends ActionController
37
{
38
    /**
39
     * @var int
40
     */
41
    private const TIMEFRAME_THISWEEK = 0;
42
43
    /**
44
     * @var int
45
     */
46
    private const TIMEFRAME_LASTWEEK = 1;
47
48
    /**
49
     * @var int
50
     */
51
    private const TIMEFRAME_LASTSEVENDAYS = 2;
52
53
    /**
54
     * @var int
55
     */
56
    private const TIMEFRAME_THISMONTH = 10;
57
58
    /**
59
     * @var int
60
     */
61
    private const TIMEFRAME_LASTMONTH = 11;
62
63
    /**
64
     * @var int
65
     */
66
    private const TIMEFRAME_LAST31DAYS = 12;
67
68
    /**
69
     * @var int
70
     */
71
    private const TIMEFRAME_CUSTOM = 30;
72
73
    /**
74
     * @var \TYPO3\CMS\Belog\Domain\Repository\LogEntryRepository
75
     */
76
    protected $logEntryRepository;
77
78
    /**
79
     * @var \TYPO3\CMS\Belog\Domain\Repository\WorkspaceRepository
80
     */
81
    protected $workspaceRepository;
82
83
    /**
84
     * @param \TYPO3\CMS\Belog\Domain\Repository\LogEntryRepository $logEntryRepository
85
     */
86
    public function injectLogEntryRepository(LogEntryRepository $logEntryRepository)
87
    {
88
        $this->logEntryRepository = $logEntryRepository;
89
    }
90
91
    /**
92
     * @param \TYPO3\CMS\Belog\Domain\Repository\WorkspaceRepository $workspaceRepository
93
     */
94
    public function injectWorkspaceRepository(WorkspaceRepository $workspaceRepository)
95
    {
96
        $this->workspaceRepository = $workspaceRepository;
97
    }
98
99
    /**
100
     * Initialize list action
101
     */
102
    public function initializeListAction()
103
    {
104
        if (!isset($this->settings['dateFormat'])) {
105
            $this->settings['dateFormat'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'd-m-Y';
106
        }
107
        if (!isset($this->settings['timeFormat'])) {
108
            $this->settings['timeFormat'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
109
        }
110
        // Static format needed for date picker (flatpickr), see BackendController::generateJavascript() and #91606
111
        $this->settings['dateTimeFormat'] = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? 'h:m m-d-Y' : 'h:m d-m-Y');
112
        $constraintConfiguration = $this->arguments->getArgument('constraint')->getPropertyMappingConfiguration();
113
        $constraintConfiguration->allowAllProperties();
114
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
115
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/GlobalEventHandler');
116
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Belog/BackendLog');
117
    }
118
119
    /**
120
     * Show general information and the installed modules
121
     *
122
     * @param Constraint $constraint
123
     * @param int $pageId
124
     * @param string $layout
125
     */
126
    public function listAction(Constraint $constraint = null, int $pageId = null, string $layout = 'Default'): ResponseInterface
127
    {
128
        // Constraint object handling:
129
        // If there is none from GET, try to get it from BE user data, else create new
130
        if ($constraint === null) {
131
            $constraint = $this->getConstraintFromBeUserData();
132
        } else {
133
            $this->persistConstraintInBeUserData($constraint);
134
        }
135
        $constraint->setPageId($pageId);
136
        $this->resetConstraintsOnMemoryExhaustionError();
137
        $this->setStartAndEndTimeFromTimeSelector($constraint);
138
        $this->forceWorkspaceSelectionIfInWorkspace($constraint);
139
        $logEntries = $this->logEntryRepository->findByConstraint($constraint);
140
        $groupedLogEntries = $this->groupLogEntriesByPageAndDay($logEntries, $constraint->getGroupByPage());
141
        $this->view->assignMultiple([
142
            'pageId' => $pageId,
143
            'layout' => $layout,
144
            'groupedLogEntries' => $groupedLogEntries,
145
            'constraint' => $constraint,
146
            'userGroups' => $this->createUserAndGroupListForSelectOptions(),
147
            'workspaces' => $this->createWorkspaceListForSelectOptions(),
148
            'pageDepths' => $this->createPageDepthOptions(),
149
        ]);
150
151
        return $this->htmlResponse($this->view->render());
152
    }
153
154
    /**
155
     * Delete all log entries that share the same message with the log entry given
156
     * in $errorUid
157
     *
158
     * @param int $errorUid
159
     */
160
    public function deleteMessageAction(int $errorUid)
161
    {
162
        /** @var \TYPO3\CMS\Belog\Domain\Model\LogEntry $logEntry */
163
        $logEntry = $this->logEntryRepository->findByUid($errorUid);
164
        if (!$logEntry) {
0 ignored issues
show
introduced by
$logEntry is of type TYPO3\CMS\Belog\Domain\Model\LogEntry, thus it always evaluated to true.
Loading history...
165
            $this->addFlashMessage(LocalizationUtility::translate('actions.delete.noRowFound', 'belog') ?? '', '', AbstractMessage::WARNING);
166
            $this->redirect('list');
167
        }
168
        $numberOfDeletedRows = $this->logEntryRepository->deleteByMessageDetails($logEntry);
169
        $this->addFlashMessage(sprintf(LocalizationUtility::translate('actions.delete.message', 'belog') ?? '', $numberOfDeletedRows));
170
        $this->redirect('list');
171
    }
172
173
    /**
174
     * Get module states (the constraint object) from user data
175
     *
176
     * @return Constraint
177
     */
178
    protected function getConstraintFromBeUserData()
179
    {
180
        $serializedConstraint = $GLOBALS['BE_USER']->getModuleData(static::class);
181
        $constraint = null;
182
        if (is_string($serializedConstraint) && !empty($serializedConstraint)) {
183
            $constraint = @unserialize($serializedConstraint, ['allowed_classes' => [Constraint::class, \DateTime::class]]);
184
        }
185
        return $constraint ?: GeneralUtility::makeInstance(Constraint::class);
186
    }
187
188
    /**
189
     * Save current constraint object in be user settings (uC)
190
     *
191
     * @param Constraint $constraint
192
     */
193
    protected function persistConstraintInBeUserData(Constraint $constraint)
194
    {
195
        $GLOBALS['BE_USER']->pushModuleData(static::class, serialize($constraint));
196
    }
197
198
    /**
199
     * In case the script execution fails, because the user requested too many results
200
     * (memory exhaustion in php), reset the constraints in be user settings, so
201
     * the belog can be accessed again in the next call.
202
     */
203
    protected function resetConstraintsOnMemoryExhaustionError()
204
    {
205
        $reservedMemory = new \SplFixedArray(187500); // 3M
206
        register_shutdown_function(function () use (&$reservedMemory): void {
207
            $reservedMemory = null; // free the reserved memory
208
            $error = error_get_last();
209
            if (strpos($error['message'], 'Allowed memory size of') !== false) {
210
                $constraint = GeneralUtility::makeInstance(Constraint::class);
211
                $this->persistConstraintInBeUserData($constraint);
212
            }
213
        });
214
    }
215
216
    /**
217
     * Create a sorted array for day and page view from
218
     * the query result of the sys log repository.
219
     *
220
     * If group by page is FALSE, pid is always -1 (will render a flat list),
221
     * otherwise the output is split by pages.
222
     * '12345' is a sub array to split entries by day, number is first second of day
223
     *
224
     * [pid][dayTimestamp][items]
225
     *
226
     * @param QueryResultInterface $logEntries
227
     * @param bool $groupByPage Whether or not log entries should be grouped by page
228
     * @return array
229
     */
230
    protected function groupLogEntriesByPageAndDay(QueryResultInterface $logEntries, $groupByPage = false)
231
    {
232
        $targetStructure = [];
233
        /** @var LogEntry $entry */
234
        foreach ($logEntries as $entry) {
235
            // Create page split list or flat list
236
            if ($groupByPage) {
237
                $pid = $entry->getEventPid();
238
            } else {
239
                $pid = -1;
240
            }
241
            // Create array if it is not defined yet
242
            if (!is_array($targetStructure[$pid])) {
243
                $targetStructure[$pid] = [];
244
            }
245
            // Get day timestamp of log entry and create sub array if needed
246
            $timestampDay = strtotime(strftime('%d.%m.%Y', $entry->getTstamp()) ?: '');
247
            if (!is_array($targetStructure[$pid][$timestampDay])) {
248
                $targetStructure[$pid][$timestampDay] = [];
249
            }
250
            // Add row
251
            $targetStructure[$pid][$timestampDay][] = $entry;
252
        }
253
        ksort($targetStructure);
254
        return $targetStructure;
255
    }
256
257
    /**
258
     * Create options for the user / group drop down.
259
     * This is not moved to a repository by intention to not mix up this 'meta' data
260
     * with real repository work
261
     *
262
     * @return array Key is the option name, value its label
263
     */
264
    protected function createUserAndGroupListForSelectOptions()
265
    {
266
        $userGroupArray = [];
267
        // Two meta entries: 'all' and 'self'
268
        $userGroupArray[0] = LocalizationUtility::translate('allUsers', 'Belog');
269
        $userGroupArray[-1] = LocalizationUtility::translate('self', 'Belog');
270
        // List of groups, key is gr-'uid'
271
        $groups = BackendUtility::getGroupNames();
272
        foreach ($groups as $group) {
273
            $userGroupArray['gr-' . $group['uid']] = LocalizationUtility::translate('group', 'Belog') . ' ' . $group['title'];
274
        }
275
        // List of users, key is us-'uid'
276
        $users = BackendUtility::getUserNames();
277
        foreach ($users as $user) {
278
            $userGroupArray['us-' . $user['uid']] = LocalizationUtility::translate('user', 'Belog') . ' ' . $user['username'];
279
        }
280
        return $userGroupArray;
281
    }
282
283
    /**
284
     * Create options for the workspace selector
285
     *
286
     * @return array Key is uid of workspace, value its label
287
     */
288
    protected function createWorkspaceListForSelectOptions()
289
    {
290
        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
291
            return [];
292
        }
293
        $workspaceArray = [];
294
        // Two meta entries: 'all' and 'live'
295
        $workspaceArray[-99] = LocalizationUtility::translate('any', 'Belog');
296
        $workspaceArray[0] = LocalizationUtility::translate('live', 'Belog');
297
        $workspaces = $this->workspaceRepository->findAll();
298
        /** @var \TYPO3\CMS\Belog\Domain\Model\Workspace $workspace */
299
        foreach ($workspaces as $workspace) {
300
            $workspaceArray[$workspace->getUid()] = $workspace->getUid() . ': ' . $workspace->getTitle();
301
        }
302
        return $workspaceArray;
303
    }
304
305
    /**
306
     * If the user is in a workspace different than LIVE,
307
     * we force to show only log entries from the selected workspace,
308
     * and the workspace selector is not shown.
309
     *
310
     * @param Constraint $constraint
311
     */
312
    protected function forceWorkspaceSelectionIfInWorkspace(Constraint $constraint)
313
    {
314
        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
315
            $this->view->assign('showWorkspaceSelector', false);
316
        } elseif ($GLOBALS['BE_USER']->workspace !== 0) {
317
            $constraint->setWorkspaceUid($GLOBALS['BE_USER']->workspace);
318
            $this->view->assign('showWorkspaceSelector', false);
319
        } else {
320
            $this->view->assign('showWorkspaceSelector', true);
321
        }
322
    }
323
324
    /**
325
     * Create options for the 'depth of page levels' selector.
326
     * This is shown if the module is displayed in page -> info
327
     *
328
     * @return array Key is depth identifier (1 = One level), value the localized select option label
329
     */
330
    protected function createPageDepthOptions()
331
    {
332
        $options = [
333
            0 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0', 'lang'),
334
            1 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1', 'lang'),
335
            2 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2', 'lang'),
336
            3 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3', 'lang'),
337
            4 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4', 'lang'),
338
            999 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi', 'lang')
339
        ];
340
        return $options;
341
    }
342
343
    /**
344
     * Calculate the start- and end timestamp from the different time selector options
345
     *
346
     * @param Constraint $constraint
347
     */
348
    protected function setStartAndEndTimeFromTimeSelector(Constraint $constraint)
349
    {
350
        $startTime = 0;
351
        $endTime = $GLOBALS['EXEC_TIME'];
352
        // @TODO: Refactor this construct
353
        switch ($constraint->getTimeFrame()) {
354
            case self::TIMEFRAME_THISWEEK:
355
                // This week
356
                $week = (date('w') ?: 7) - 1;
357
                $startTime = mktime(0, 0, 0) - $week * 3600 * 24;
358
                break;
359
            case self::TIMEFRAME_LASTWEEK:
360
                // Last week
361
                $week = (date('w') ?: 7) - 1;
362
                $startTime = mktime(0, 0, 0) - ($week + 7) * 3600 * 24;
363
                $endTime = mktime(0, 0, 0) - $week * 3600 * 24;
364
                break;
365
            case self::TIMEFRAME_LASTSEVENDAYS:
366
                // Last 7 days
367
                $startTime = mktime(0, 0, 0) - 7 * 3600 * 24;
368
                break;
369
            case self::TIMEFRAME_THISMONTH:
370
                // This month
371
                $startTime = mktime(0, 0, 0, (int)date('m'), 1);
372
                break;
373
            case self::TIMEFRAME_LASTMONTH:
374
                // Last month
375
                $startTime = mktime(0, 0, 0, (int)date('m') - 1, 1);
376
                $endTime = mktime(0, 0, 0, (int)date('m'), 1);
377
                break;
378
            case self::TIMEFRAME_LAST31DAYS:
379
                // Last 31 days
380
                $startTime = mktime(0, 0, 0) - 31 * 3600 * 24;
381
                break;
382
            case self::TIMEFRAME_CUSTOM:
383
                $startTime = $constraint->getManualDateStart() ? $constraint->getManualDateStart()->getTimestamp() : 0;
384
                $endTime = $constraint->getManualDateStop() ? $constraint->getManualDateStop()->getTimestamp() : 0;
385
                if ($endTime <= $startTime) {
386
                    $endTime = $GLOBALS['EXEC_TIME'];
387
                }
388
                break;
389
            default:
390
        }
391
        $constraint->setStartTimestamp($startTime);
392
        $constraint->setEndTimestamp($endTime);
393
    }
394
}
395