[BUGFIX] Refactor record querying in deep nested structures in recycler
[Packages/TYPO3.CMS.git] / typo3 / sysext / recycler / Classes / Controller / DeletedRecordsController.php
1 <?php
2
3 namespace TYPO3\CMS\Recycler\Controller;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Cache\CacheManager;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\DataHandling\DataHandler;
22 use TYPO3\CMS\Core\History\RecordHistoryStore;
23 use TYPO3\CMS\Core\Imaging\Icon;
24 use TYPO3\CMS\Core\Imaging\IconFactory;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
27
28 /**
29 * Deleted Records View
30 */
31 class DeletedRecordsController
32 {
33 /**
34 * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
35 */
36 protected $runtimeCache = null;
37
38 /**
39 * @var DataHandler
40 */
41 protected $tce;
42
43 public function __construct()
44 {
45 $this->runtimeCache = $this->getMemoryCache();
46 $this->tce = GeneralUtility::makeInstance(DataHandler::class);
47 }
48
49 /**
50 * Transforms the rows for the deleted records
51 *
52 * @param array $deletedRowsArray Array with table as key and array with all deleted rows
53 * @param int $totalDeleted Number of deleted records in total
54 * @return array JSON array
55 */
56 public function transform($deletedRowsArray, $totalDeleted)
57 {
58 $total = 0;
59 $jsonArray = [
60 'rows' => []
61 ];
62
63 if (is_array($deletedRowsArray)) {
64 $lang = $this->getLanguageService();
65 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
66
67 foreach ($deletedRowsArray as $table => $rows) {
68 $total += count($deletedRowsArray[$table]);
69 foreach ($rows as $row) {
70 $pageTitle = $this->getPageTitle((int)$row['pid']);
71 $backendUserName = $this->getBackendUser((int)$row[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']]);
72
73 $userIdWhoDeleted = $this->getUserWhoDeleted($table, (int)$row['uid']);
74
75 $jsonArray['rows'][] = [
76 'uid' => $row['uid'],
77 'pid' => $row['pid'],
78 'icon' => $iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render(),
79 'pageTitle' => $pageTitle,
80 'table' => $table,
81 'crdate' => BackendUtility::datetime($row[$GLOBALS['TCA'][$table]['ctrl']['crdate']]),
82 'tstamp' => BackendUtility::datetime($row[$GLOBALS['TCA'][$table]['ctrl']['tstamp']]),
83 'owner' => htmlspecialchars($backendUserName),
84 'owner_uid' => $row[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']],
85 'tableTitle' => $lang->sL($GLOBALS['TCA'][$table]['ctrl']['title']),
86 'title' => htmlspecialchars(BackendUtility::getRecordTitle($table, $row)),
87 'path' => RecyclerUtility::getRecordPath($row['pid']),
88 'delete_user_uid' => $userIdWhoDeleted,
89 'delete_user' => $this->getBackendUser($userIdWhoDeleted),
90 'isParentDeleted' => $table === 'pages' ? RecyclerUtility::isParentPageDeleted($row['pid']) : false
91 ];
92 }
93 }
94 }
95 $jsonArray['total'] = $totalDeleted;
96 return $jsonArray;
97 }
98
99 /**
100 * Gets the page title of the given page id
101 *
102 * @param int $pageId
103 * @return string
104 */
105 protected function getPageTitle($pageId)
106 {
107 $cacheId = 'recycler-pagetitle-' . $pageId;
108 if ($this->runtimeCache->has($cacheId)) {
109 $pageTitle = $this->runtimeCache->get($cacheId);
110 } else {
111 if ($pageId === 0) {
112 $pageTitle = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
113 } else {
114 $recordInfo = $this->tce->recordInfo('pages', $pageId, 'title');
115 $pageTitle = $recordInfo['title'];
116 }
117 $this->runtimeCache->set($cacheId, $pageTitle);
118 }
119 return $pageTitle;
120 }
121
122 /**
123 * Gets the username of a given backend user
124 *
125 * @param int $userId uid of user
126 * @return string
127 */
128 protected function getBackendUser(int $userId): string
129 {
130 if ($userId === 0) {
131 return '';
132 }
133 $cacheId = 'recycler-user-' . $userId;
134 if ($this->runtimeCache->has($cacheId)) {
135 $username = $this->runtimeCache->get($cacheId);
136 } else {
137 $backendUser = BackendUtility::getRecord('be_users', $userId, 'username', '', false);
138 $username = $backendUser['username'];
139 $this->runtimeCache->set($cacheId, $username);
140 }
141 return $username;
142 }
143
144 /**
145 * Get the user uid of the user who deleted the record
146 *
147 * @param string $table table name
148 * @param int $uid uid of record
149 * @return int
150 */
151 protected function getUserWhoDeleted(string $table, int $uid): int
152 {
153 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
154 ->getQueryBuilderForTable('sys_history');
155 $queryBuilder->select('userid')
156 ->from('sys_history')
157 ->where(
158 $queryBuilder->expr()->eq(
159 'tablename',
160 $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
161 ),
162 $queryBuilder->expr()->eq(
163 'usertype',
164 $queryBuilder->createNamedParameter('BE', \PDO::PARAM_STR)
165 ),
166 $queryBuilder->expr()->eq(
167 'recuid',
168 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
169 ),
170 $queryBuilder->expr()->eq(
171 'actiontype',
172 $queryBuilder->createNamedParameter(RecordHistoryStore::ACTION_DELETE, \PDO::PARAM_INT)
173 )
174 )
175 ->setMaxResults(1);
176
177 return (int)$queryBuilder->execute()->fetchColumn(0);
178 }
179
180 /**
181 * Returns an instance of LanguageService
182 *
183 * @return \TYPO3\CMS\Core\Localization\LanguageService
184 */
185 protected function getLanguageService()
186 {
187 return $GLOBALS['LANG'];
188 }
189
190 /**
191 * Create and returns an instance of the CacheManager
192 *
193 * @return CacheManager
194 */
195 protected function getCacheManager()
196 {
197 return GeneralUtility::makeInstance(CacheManager::class);
198 }
199
200 /**
201 * Gets an instance of the memory cache.
202 *
203 * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
204 */
205 protected function getMemoryCache()
206 {
207 return $this->getCacheManager()->getCache('cache_runtime');
208 }
209 }