1<?php
2namespace TYPO3\CMS\Beuser\Controller;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17use Psr\Http\Message\ResponseInterface;
18use Psr\Http\Message\ServerRequestInterface;
19use TYPO3\CMS\Backend\Utility\BackendUtility;
20use TYPO3\CMS\Core\DataHandling\DataHandler;
21use TYPO3\CMS\Core\Http\HtmlResponse;
22use TYPO3\CMS\Core\Imaging\Icon;
23use TYPO3\CMS\Core\Imaging\IconFactory;
24use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
25use TYPO3\CMS\Core\Utility\GeneralUtility;
26use TYPO3\CMS\Fluid\View\StandaloneView;
27
28/**
29 * This class extends the permissions module in the TYPO3 Backend to provide
30 * convenient methods of editing of page permissions (including page ownership
31 * (user and group)) via new AjaxRequestHandler facility
32 * @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
33 */
34class PermissionAjaxController
35{
36    /**
37     * The local configuration array
38     *
39     * @var array
40     */
41    protected $conf;
42
43    /**
44     * @var IconFactory
45     */
46    protected $iconFactory;
47
48    /**
49     * The constructor of this class
50     */
51    public function __construct()
52    {
53        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
54        $this->getLanguageService()->includeLLFile('EXT:beuser/Resources/Private/Language/locallang_mod_permission.xlf');
55    }
56
57    /**
58     * The main dispatcher function. Collect data and prepare HTML output.
59     *
60     * @param ServerRequestInterface $request
61     * @return ResponseInterface
62     */
63    public function dispatch(ServerRequestInterface $request): ResponseInterface
64    {
65        $parsedBody = $request->getParsedBody();
66        $this->conf = [
67            'page' => $parsedBody['page'] ?? null,
68            'who' => $parsedBody['who'] ?? null,
69            'mode' => $parsedBody['mode'] ?? null,
70            'bits' => (int)($parsedBody['bits'] ?? 0),
71            'permissions' => (int)($parsedBody['permissions'] ?? 0),
72            'action' => $parsedBody['action'] ?? null,
73            'ownerUid' => (int)($parsedBody['ownerUid'] ?? 0),
74            'username' => $parsedBody['username'] ?? null,
75            'groupUid' => (int)($parsedBody['groupUid'] ?? 0),
76            'groupname' => $parsedBody['groupname'] ?? '',
77            'editLockState' => (int)($parsedBody['editLockState'] ?? 0),
78            'new_owner_uid' => (int)($parsedBody['newOwnerUid'] ?? 0),
79            'new_group_uid' => (int)($parsedBody['newGroupUid'] ?? 0),
80        ];
81
82        $extPath = ExtensionManagementUtility::extPath('beuser');
83
84        $view = GeneralUtility::makeInstance(StandaloneView::class);
85        $view->setPartialRootPaths(['default' => ExtensionManagementUtility::extPath('beuser') . 'Resources/Private/Partials']);
86        $view->assign('pageId', $this->conf['page']);
87
88        $response = new HtmlResponse('');
89
90        // Basic test for required value
91        if ($this->conf['page'] <= 0) {
92            $response->getBody()->write('This script cannot be called directly');
93            return $response->withStatus(500);
94        }
95
96        $content = '';
97        // Init TCE for execution of update
98        $tce = GeneralUtility::makeInstance(DataHandler::class);
99        // Determine the scripts to execute
100        switch ($this->conf['action']) {
101            case 'show_change_owner_selector':
102                $content = $this->renderUserSelector($this->conf['page'], $this->conf['ownerUid'], $this->conf['username']);
103                break;
104            case 'change_owner':
105                $userId = $this->conf['new_owner_uid'];
106                if (is_int($userId)) {
107                    // Prepare data to change
108                    $data = [];
109                    $data['pages'][$this->conf['page']]['perms_userid'] = $userId;
110                    // Execute TCE Update
111                    $tce->start($data, []);
112                    $tce->process_datamap();
113
114                    $view->setTemplatePathAndFilename($extPath . 'Resources/Private/Templates/PermissionAjax/ChangeOwner.html');
115                    $view->assign('userId', $userId);
116                    $usernameArray = BackendUtility::getUserNames('username', ' AND uid = ' . $userId);
117                    $view->assign('username', $usernameArray[$userId]['username']);
118                    $content = $view->render();
119                } else {
120                    $response->getBody()->write('An error occurred: No page owner uid specified');
121                    $response = $response->withStatus(500);
122                }
123                break;
124            case 'show_change_group_selector':
125                $content = $this->renderGroupSelector($this->conf['page'], $this->conf['groupUid'], $this->conf['groupname']);
126                break;
127            case 'change_group':
128                $groupId = $this->conf['new_group_uid'];
129                if (is_int($groupId)) {
130                    // Prepare data to change
131                    $data = [];
132                    $data['pages'][$this->conf['page']]['perms_groupid'] = $groupId;
133                    // Execute TCE Update
134                    $tce->start($data, []);
135                    $tce->process_datamap();
136
137                    $view->setTemplatePathAndFilename($extPath . 'Resources/Private/Templates/PermissionAjax/ChangeGroup.html');
138                    $view->assign('groupId', $groupId);
139                    $groupnameArray = BackendUtility::getGroupNames('title', ' AND uid = ' . $groupId);
140                    $view->assign('groupname', $groupnameArray[$groupId]['title']);
141                    $content = $view->render();
142                } else {
143                    $response->getBody()->write('An error occurred: No page group uid specified');
144                    $response = $response->withStatus(500);
145                }
146                break;
147            case 'toggle_edit_lock':
148                // Prepare data to change
149                $data = [];
150                $data['pages'][$this->conf['page']]['editlock'] = $this->conf['editLockState'] === 1 ? 0 : 1;
151                // Execute TCE Update
152                $tce->start($data, []);
153                $tce->process_datamap();
154                $content = $this->renderToggleEditLock($this->conf['page'], $data['pages'][$this->conf['page']]['editlock']);
155                break;
156            default:
157                if ($this->conf['mode'] === 'delete') {
158                    $this->conf['permissions'] = (int)($this->conf['permissions'] - $this->conf['bits']);
159                } else {
160                    $this->conf['permissions'] = (int)($this->conf['permissions'] + $this->conf['bits']);
161                }
162                // Prepare data to change
163                $data = [];
164                $data['pages'][$this->conf['page']]['perms_' . $this->conf['who']] = $this->conf['permissions'];
165                // Execute TCE Update
166                $tce->start($data, []);
167                $tce->process_datamap();
168
169                $view->setTemplatePathAndFilename($extPath . 'Resources/Private/Templates/PermissionAjax/ChangePermission.html');
170                $view->assign('permission', $this->conf['permissions']);
171                $view->assign('scope', $this->conf['who']);
172                $content = $view->render();
173        }
174        $response->getBody()->write($content);
175        return $response;
176    }
177
178    /**
179     * Generate the user selector element
180     *
181     * @param int $page The page id to change the user for
182     * @param int $ownerUid The page owner uid
183     * @param string $username The username to display
184     * @return string The html select element
185     */
186    protected function renderUserSelector($page, $ownerUid, $username = '')
187    {
188        $page = (int)$page;
189        $ownerUid = (int)$ownerUid;
190        // Get usernames
191        $beUsers = BackendUtility::getUserNames();
192        // Owner selector:
193        $options = '';
194        // Loop through the users
195        foreach ($beUsers as $uid => $row) {
196            $uid = (int)$uid;
197            $selected = $uid === $ownerUid ? ' selected="selected"' : '';
198            $options .= '<option value="' . $uid . '"' . $selected . '>' . htmlspecialchars($row['username']) . '</option>';
199        }
200        $elementId = 'o_' . $page;
201        $options = '<option value="0"></option>' . $options;
202        $selector = '<select name="new_page_owner" id="new_page_owner">' . $options . '</select>';
203        $saveButton = '<a class="saveowner btn btn-default" data-page="' . $page . '" data-owner="' . $ownerUid
204                        . '" data-element-id="' . $elementId . '" title="Change owner">'
205                        . $this->iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL)->render()
206                        . '</a>';
207        $cancelButton = '<a class="restoreowner btn btn-default" data-page="' . $page . '"  data-owner="' . $ownerUid
208                        . '" data-element-id="' . $elementId . '"'
209                        . (!empty($username) ? ' data-username="' . htmlspecialchars($username) . '"' : '')
210                        . ' title="Cancel">'
211                        . $this->iconFactory->getIcon('actions-close', Icon::SIZE_SMALL)->render()
212                        . '</a>';
213        return '<span id="' . $elementId . '">'
214            . $selector
215            . '<span class="btn-group">'
216            . $saveButton
217            . $cancelButton
218            . '</span>'
219            . '</span>';
220    }
221
222    /**
223     * Generate the group selector element
224     *
225     * @param int $page The page id to change the user for
226     * @param int $groupUid The page group uid
227     * @param string $groupname The groupname to display
228     * @return string The html select element
229     */
230    protected function renderGroupSelector($page, $groupUid, $groupname = '')
231    {
232        $page = (int)$page;
233        $groupUid = (int)$groupUid;
234
235        // Get group names
236        $beGroupsO = $beGroups = BackendUtility::getGroupNames();
237        // Group selector:
238        $options = '';
239        // flag: is set if the page-groupid equals one from the group-list
240        $userset = 0;
241        // Loop through the groups
242        foreach ($beGroups as $uid => $row) {
243            $uid = (int)$uid;
244            if ($uid === $groupUid) {
245                $userset = 1;
246                $selected = ' selected="selected"';
247            } else {
248                $selected = '';
249            }
250            $options .= '<option value="' . $uid . '"' . $selected . '>' . htmlspecialchars($row['title']) . '</option>';
251        }
252        // If the group was not set AND there is a group for the page
253        if (!$userset && $groupUid) {
254            $options = '<option value="' . $groupUid . '" selected="selected">' .
255                htmlspecialchars($beGroupsO[$groupUid]['title']) . '</option>' . $options;
256        }
257        $elementId = 'g_' . $page;
258        $options = '<option value="0"></option>' . $options;
259        $selector = '<select name="new_page_group" id="new_page_group">' . $options . '</select>';
260        $saveButton = '<a class="savegroup btn btn-default" data-page="' . $page . '" data-group-id="' . $groupUid
261                        . '" data-element-id="' . $elementId . '" title="Change group">'
262                        . $this->iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL)->render()
263                        . '</a>';
264        $cancelButton = '<a class="restoregroup btn btn-default" data-page="' . $page . '" data-group-id="' . $groupUid
265                        . '" data-element-id="' . $elementId . '"'
266                        . (!empty($groupname) ? ' data-groupname="' . htmlspecialchars($groupname) . '"' : '')
267                        . ' title="Cancel">'
268                        . $this->iconFactory->getIcon('actions-close', Icon::SIZE_SMALL)->render()
269                        . '</a>';
270        return '<span id="' . $elementId . '">'
271            . $selector
272            . '<span class="btn-group">'
273            . $saveButton
274            . $cancelButton
275            . '</span>'
276            . '</span>';
277    }
278
279    /**
280     * Print the string with the new edit lock state of a page record
281     *
282     * @param int $page The TYPO3 page id
283     * @param string $editLockState The state of the TYPO3 page (locked, unlocked)
284     * @return string The new edit lock string wrapped in HTML
285     */
286    protected function renderToggleEditLock($page, $editLockState)
287    {
288        $page = (int)$page;
289        if ($editLockState === 1) {
290            $ret = '<span id="el_' . $page . '"><a class="editlock btn btn-default" data-page="' . $page
291                    . '" data-lockstate="1" title="The page and all content is locked for editing by all non-Admin users.">'
292                    . $this->iconFactory->getIcon('actions-lock', Icon::SIZE_SMALL)->render() . '</a></span>';
293        } else {
294            $ret = '<span id="el_' . $page . '"><a class="editlock btn btn-default" data-page="' . $page .
295                    '" data-lockstate="0" title="Enable the &raquo;Admin-only&laquo; edit lock for this page">'
296                    . $this->iconFactory->getIcon('actions-unlock', Icon::SIZE_SMALL)->render() . '</a></span>';
297        }
298        return $ret;
299    }
300
301    /**
302     * @return \TYPO3\CMS\Core\Localization\LanguageService
303     */
304    protected function getLanguageService()
305    {
306        return $GLOBALS['LANG'];
307    }
308
309    /**
310     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
311     */
312    protected function getBackendUser()
313    {
314        return $GLOBALS['BE_USER'];
315    }
316}
317