Passed
Push — master ( d99bd7...d26958 )
by
unknown
12:12
created

BackendLogController::groupLogEntriesDay()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 20
rs 9.6111
c 0
b 0
f 0
cc 5
nc 5
nop 1
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
 * Show log entries from sys_log
34
 *
35
 * @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
36
 */
37
class BackendLogController extends ActionController
38
{
39
    /**
40
     * @var \TYPO3\CMS\Belog\Domain\Repository\LogEntryRepository
41
     */
42
    protected $logEntryRepository;
43
44
    /**
45
     * @var \TYPO3\CMS\Belog\Domain\Repository\WorkspaceRepository
46
     */
47
    protected $workspaceRepository;
48
49
    /**
50
     * @param \TYPO3\CMS\Belog\Domain\Repository\LogEntryRepository $logEntryRepository
51
     */
52
    public function injectLogEntryRepository(LogEntryRepository $logEntryRepository)
53
    {
54
        $this->logEntryRepository = $logEntryRepository;
55
    }
56
57
    /**
58
     * @param \TYPO3\CMS\Belog\Domain\Repository\WorkspaceRepository $workspaceRepository
59
     */
60
    public function injectWorkspaceRepository(WorkspaceRepository $workspaceRepository)
61
    {
62
        $this->workspaceRepository = $workspaceRepository;
63
    }
64
65
    /**
66
     * Initialize list action
67
     */
68
    public function initializeListAction()
69
    {
70
        if (!isset($this->settings['dateFormat'])) {
71
            $this->settings['dateFormat'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'd-m-Y';
72
        }
73
        if (!isset($this->settings['timeFormat'])) {
74
            $this->settings['timeFormat'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
75
        }
76
        // Static format needed for date picker (flatpickr), see BackendController::generateJavascript() and #91606
77
        $this->settings['dateTimeFormat'] = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? 'h:m m-d-Y' : 'h:m d-m-Y');
78
        $constraintConfiguration = $this->arguments->getArgument('constraint')->getPropertyMappingConfiguration();
79
        $constraintConfiguration->allowAllProperties();
80
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
81
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/GlobalEventHandler');
82
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/DateTimePicker');
83
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Belog/BackendLog');
84
    }
85
86
    /**
87
     * Show general information and the installed modules
88
     *
89
     * @param Constraint|null $constraint
90
     * @param int|null $pageId
91
     * @param string $layout
92
     * @param string $operation
93
     */
94
    public function listAction(Constraint $constraint = null, int $pageId = null, string $layout = 'Default', string $operation = ''): ResponseInterface
95
    {
96
        if ($operation === 'reset-filters') {
97
            $constraint = new Constraint();
98
        } elseif ($constraint === null) {
99
            $constraint = $this->getConstraintFromBeUserData();
100
        }
101
        $this->persistConstraintInBeUserData($constraint);
102
        $constraint->setPageId($pageId);
103
        $this->resetConstraintsOnMemoryExhaustionError();
104
        $this->setStartAndEndTimeFromTimeSelector($constraint);
105
        $this->forceWorkspaceSelectionIfInWorkspace($constraint);
106
        $logEntries = $this->logEntryRepository->findByConstraint($constraint);
107
        $groupedLogEntries = $this->groupLogEntriesDay($logEntries);
108
        $this->view->assignMultiple([
109
            'pageId' => $pageId,
110
            'layout' => $layout,
111
            'groupedLogEntries' => $groupedLogEntries,
112
            'constraint' => $constraint,
113
            'userGroups' => $this->createUserAndGroupListForSelectOptions(),
114
            'workspaces' => $this->createWorkspaceListForSelectOptions(),
115
            'pageDepths' => $this->createPageDepthOptions(),
116
        ]);
117
118
        return $this->htmlResponse($this->view->render());
119
    }
120
121
    /**
122
     * Delete all log entries that share the same message with the log entry given
123
     * in $errorUid
124
     *
125
     * @param int $errorUid
126
     */
127
    public function deleteMessageAction(int $errorUid)
128
    {
129
        /** @var \TYPO3\CMS\Belog\Domain\Model\LogEntry $logEntry */
130
        $logEntry = $this->logEntryRepository->findByUid($errorUid);
131
        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...
132
            $this->addFlashMessage(LocalizationUtility::translate('actions.delete.noRowFound', 'belog') ?? '', '', AbstractMessage::WARNING);
133
            $this->redirect('list');
134
        }
135
        $numberOfDeletedRows = $this->logEntryRepository->deleteByMessageDetails($logEntry);
136
        $this->addFlashMessage(sprintf(LocalizationUtility::translate('actions.delete.message', 'belog') ?? '', $numberOfDeletedRows));
137
        $this->redirect('list');
138
    }
139
140
    /**
141
     * Get module states (the constraint object) from user data
142
     *
143
     * @return Constraint
144
     */
145
    protected function getConstraintFromBeUserData()
146
    {
147
        $serializedConstraint = $GLOBALS['BE_USER']->getModuleData(static::class);
148
        $constraint = null;
149
        if (is_string($serializedConstraint) && !empty($serializedConstraint)) {
150
            $constraint = @unserialize($serializedConstraint, ['allowed_classes' => [Constraint::class, \DateTime::class]]);
151
        }
152
        return $constraint ?: GeneralUtility::makeInstance(Constraint::class);
153
    }
154
155
    /**
156
     * Save current constraint object in be user settings (uC)
157
     *
158
     * @param Constraint $constraint
159
     */
160
    protected function persistConstraintInBeUserData(Constraint $constraint)
161
    {
162
        $GLOBALS['BE_USER']->pushModuleData(static::class, serialize($constraint));
163
    }
164
165
    /**
166
     * In case the script execution fails, because the user requested too many results
167
     * (memory exhaustion in php), reset the constraints in be user settings, so
168
     * the belog can be accessed again in the next call.
169
     */
170
    protected function resetConstraintsOnMemoryExhaustionError()
171
    {
172
        $reservedMemory = new \SplFixedArray(187500); // 3M
173
        register_shutdown_function(function () use (&$reservedMemory): void {
174
            $reservedMemory = null; // free the reserved memory
175
            $error = error_get_last();
176
            if (strpos($error['message'], 'Allowed memory size of') !== false) {
177
                $constraint = GeneralUtility::makeInstance(Constraint::class);
178
                $this->persistConstraintInBeUserData($constraint);
179
            }
180
        });
181
    }
182
183
    /**
184
     * Create a sorted array for day from the query result of the sys log repository.
185
     *
186
     * pid is always -1 to render a flat list.
187
     * '12345' is a sub array to split entries by day, number is first second of day
188
     *
189
     * [pid][dayTimestamp][items]
190
     *
191
     * @param QueryResultInterface $logEntries
192
     * @return array
193
     */
194
    protected function groupLogEntriesDay(QueryResultInterface $logEntries): array
195
    {
196
        $targetStructure = [];
197
        /** @var LogEntry $entry */
198
        foreach ($logEntries as $entry) {
199
            $pid = -1;
200
            // Create array if it is not defined yet
201
            if (!is_array($targetStructure[$pid])) {
202
                $targetStructure[-1] = [];
203
            }
204
            // Get day timestamp of log entry and create sub array if needed
205
            $timestampDay = strtotime(strftime('%d.%m.%Y', $entry->getTstamp()) ?: '');
206
            if (!is_array($targetStructure[$pid][$timestampDay])) {
207
                $targetStructure[$pid][$timestampDay] = [];
208
            }
209
            // Add row
210
            $targetStructure[$pid][$timestampDay][] = $entry;
211
        }
212
        ksort($targetStructure);
213
        return $targetStructure;
214
    }
215
216
    /**
217
     * Create options for the user / group drop down.
218
     * This is not moved to a repository by intention to not mix up this 'meta' data
219
     * with real repository work
220
     *
221
     * @return array Key is the option name, value its label
222
     */
223
    protected function createUserAndGroupListForSelectOptions()
224
    {
225
        $userGroupArray = [];
226
        // Two meta entries: 'all' and 'self'
227
        $userGroupArray[0] = LocalizationUtility::translate('allUsers', 'Belog');
228
        $userGroupArray[-1] = LocalizationUtility::translate('self', 'Belog');
229
        // List of groups, key is gr-'uid'
230
        $groups = BackendUtility::getGroupNames();
231
        foreach ($groups as $group) {
232
            $userGroupArray['gr-' . $group['uid']] = LocalizationUtility::translate('group', 'Belog') . ' ' . $group['title'];
233
        }
234
        // List of users, key is us-'uid'
235
        $users = BackendUtility::getUserNames();
236
        foreach ($users as $user) {
237
            $userGroupArray['us-' . $user['uid']] = LocalizationUtility::translate('user', 'Belog') . ' ' . $user['username'];
238
        }
239
        return $userGroupArray;
240
    }
241
242
    /**
243
     * Create options for the workspace selector
244
     *
245
     * @return array Key is uid of workspace, value its label
246
     */
247
    protected function createWorkspaceListForSelectOptions()
248
    {
249
        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
250
            return [];
251
        }
252
        $workspaceArray = [];
253
        // Two meta entries: 'all' and 'live'
254
        $workspaceArray[-99] = LocalizationUtility::translate('any', 'Belog');
255
        $workspaceArray[0] = LocalizationUtility::translate('live', 'Belog');
256
        $workspaces = $this->workspaceRepository->findAll();
257
        /** @var \TYPO3\CMS\Belog\Domain\Model\Workspace $workspace */
258
        foreach ($workspaces as $workspace) {
259
            $workspaceArray[$workspace->getUid()] = $workspace->getUid() . ': ' . $workspace->getTitle();
260
        }
261
        return $workspaceArray;
262
    }
263
264
    /**
265
     * If the user is in a workspace different than LIVE,
266
     * we force to show only log entries from the selected workspace,
267
     * and the workspace selector is not shown.
268
     *
269
     * @param Constraint $constraint
270
     */
271
    protected function forceWorkspaceSelectionIfInWorkspace(Constraint $constraint)
272
    {
273
        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
274
            $this->view->assign('showWorkspaceSelector', false);
275
        } elseif ($GLOBALS['BE_USER']->workspace !== 0) {
276
            $constraint->setWorkspaceUid($GLOBALS['BE_USER']->workspace);
277
            $this->view->assign('showWorkspaceSelector', false);
278
        } else {
279
            $this->view->assign('showWorkspaceSelector', true);
280
        }
281
    }
282
283
    /**
284
     * Create options for the 'depth of page levels' selector.
285
     * This is shown if the module is displayed in page -> info
286
     *
287
     * @return array Key is depth identifier (1 = One level), value the localized select option label
288
     */
289
    protected function createPageDepthOptions()
290
    {
291
        $options = [
292
            0 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0', 'lang'),
293
            1 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1', 'lang'),
294
            2 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2', 'lang'),
295
            3 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3', 'lang'),
296
            4 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4', 'lang'),
297
            999 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi', 'lang')
298
        ];
299
        return $options;
300
    }
301
302
    /**
303
     * Calculate the start- and end timestamp
304
     *
305
     * @param Constraint $constraint
306
     */
307
    protected function setStartAndEndTimeFromTimeSelector(Constraint $constraint)
308
    {
309
        $startTime = $constraint->getManualDateStart() ? $constraint->getManualDateStart()->getTimestamp() : 0;
310
        $endTime = $constraint->getManualDateStop() ? $constraint->getManualDateStop()->getTimestamp() : 0;
311
        if ($endTime <= $startTime) {
312
            $endTime = $GLOBALS['EXEC_TIME'];
313
        }
314
        $constraint->setStartTimestamp($startTime);
315
        $constraint->setEndTimestamp($endTime);
316
    }
317
}
318