fe2cbda457243f5bda368a6a296980608657af56
[Packages/TYPO3.CMS.git] / typo3 / sysext / sys_action / Classes / ActionTask.php
1 <?php
2 namespace TYPO3\CMS\SysAction;
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
17 use Doctrine\DBAL\DBALException;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
21 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
22 use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
23 use TYPO3\CMS\Core\Imaging\Icon;
24 use TYPO3\CMS\Core\Imaging\IconFactory;
25 use TYPO3\CMS\Core\Messaging\FlashMessage;
26 use TYPO3\CMS\Core\Messaging\FlashMessageService;
27 use TYPO3\CMS\Core\Page\PageRenderer;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29 use TYPO3\CMS\Core\Utility\HttpUtility;
30
31 /**
32 * This class provides a task for the taskcenter
33 */
34 class ActionTask implements \TYPO3\CMS\Taskcenter\TaskInterface
35 {
36 /**
37 * @var \TYPO3\CMS\Taskcenter\Controller\TaskModuleController
38 */
39 protected $taskObject;
40
41 /**
42 * All hook objects get registered here for later use
43 *
44 * @var array
45 */
46 protected $hookObjects = [];
47
48 /**
49 * URL to task module
50 *
51 * @var string
52 */
53 protected $moduleUrl;
54
55 /**
56 * @var IconFactory
57 */
58 protected $iconFactory;
59
60 /**
61 * Constructor
62 */
63 public function __construct(\TYPO3\CMS\Taskcenter\Controller\TaskModuleController $taskObject)
64 {
65 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
66 $this->moduleUrl = BackendUtility::getModuleUrl('user_task');
67 $this->taskObject = $taskObject;
68 $this->getLanguageService()->includeLLFile('EXT:sys_action/Resources/Private/Language/locallang.xlf');
69 if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['sys_action']['tx_sysaction_task'])) {
70 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['sys_action']['tx_sysaction_task'] as $classRef) {
71 $this->hookObjects[] = GeneralUtility::getUserObj($classRef);
72 }
73 }
74 }
75
76 /**
77 * This method renders the task
78 *
79 * @return string The task as HTML
80 */
81 public function getTask()
82 {
83 $content = '';
84 $show = (int)GeneralUtility::_GP('show');
85 foreach ($this->hookObjects as $hookObject) {
86 if (method_exists($hookObject, 'getTask')) {
87 $show = $hookObject->getTask($show, $this);
88 }
89 }
90 // If no task selected, render the menu
91 if ($show == 0) {
92 $content .= $this->taskObject->description($this->getLanguageService()->getLL('sys_action'), $this->getLanguageService()->getLL('description'));
93 $content .= $this->renderActionList();
94 } else {
95 $record = BackendUtility::getRecord('sys_action', $show);
96 // If the action is not found
97 if (empty($record)) {
98 $this->addMessage(
99 $this->getLanguageService()->getLL('action_error-not-found'),
100 $this->getLanguageService()->getLL('action_error'),
101 FlashMessage::ERROR
102 );
103 } else {
104 // Render the task
105 $content .= $this->taskObject->description($record['title'], $record['description']);
106 // Output depends on the type
107 switch ($record['type']) {
108 case 1:
109 $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
110 $pageRenderer->loadRequireJsModule('TYPO3/CMS/SysAction/ActionTask');
111 $content .= $this->viewNewBackendUser($record);
112 break;
113 case 2:
114 $content .= $this->viewSqlQuery($record);
115 break;
116 case 3:
117 $content .= $this->viewRecordList($record);
118 break;
119 case 4:
120 $content .= $this->viewEditRecord($record);
121 break;
122 case 5:
123 $content .= $this->viewNewRecord($record);
124 break;
125 default:
126 $this->addMessage(
127 $this->getLanguageService()->getLL('action_noType'),
128 $this->getLanguageService()->getLL('action_error'),
129 FlashMessage::ERROR
130 );
131 $content .= $this->renderFlashMessages();
132 }
133 }
134 }
135 return $content;
136 }
137
138 /**
139 * General overview over the task in the taskcenter menu
140 *
141 * @return string Overview as HTML
142 */
143 public function getOverview()
144 {
145 $content = '<p>' . $this->getLanguageService()->getLL('description') . '</p>';
146 // Get the actions
147 $actionList = $this->getActions();
148 if (!empty($actionList)) {
149 $items = '';
150 // Render a single action menu item
151 foreach ($actionList as $action) {
152 $active = GeneralUtility::_GP('show') === $action['uid'] ? 'active' : '';
153 $items .= '<a class="list-group-item ' . $active . '" href="' . $action['link'] . '" title="' . htmlspecialchars($action['description']) . '">' . htmlspecialchars($action['title']) . '</a>';
154 }
155 $content .= '<div class="list-group">' . $items . '</div>';
156 }
157 return $content;
158 }
159
160 /**
161 * Get all actions of an user. Admins can see any action, all others only those
162 * which are allowed in sys_action record itself.
163 *
164 * @return array Array holding every needed information of a sys_action
165 */
166 protected function getActions()
167 {
168 $backendUser = $this->getBackendUser();
169 $actionList = [];
170
171 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_action');
172 $queryBuilder->select('sys_action.*')
173 ->from('sys_action');
174
175 if (!empty($GLOBALS['TCA']['sys_action']['ctrl']['sortby'])) {
176 $queryBuilder->orderBy('sys_action.' . $GLOBALS['TCA']['sys_action']['ctrl']['sortby']);
177 }
178
179 $queryBuilder->getRestrictions()
180 ->removeAll()
181 ->add(GeneralUtility::makeInstance(RootLevelRestriction::class, ['sys_action']));
182
183 // Editors can only see the actions which are assigned to a usergroup they belong to
184 if (!$backendUser->isAdmin()) {
185 $groupList = $backendUser->groupList ?: '0';
186
187 $queryBuilder->getRestrictions()
188 ->add(GeneralUtility::makeInstance(HiddenRestriction::class));
189
190 $queryBuilder
191 ->join(
192 'sys_action',
193 'sys_action_asgr_mm',
194 'sys_action_asgr_mm',
195 $queryBuilder->expr()->eq(
196 'sys_action_asgr_mm.uid_local',
197 $queryBuilder->quoteIdentifier('sys_action.uid')
198 )
199 )
200 ->join(
201 'sys_action_asgr_mm',
202 'be_groups',
203 'be_groups',
204 $queryBuilder->expr()->eq(
205 'sys_action_asgr_mm.uid_foreign',
206 $queryBuilder->quoteIdentifier('be_groups.uid')
207 )
208 )
209 ->where($queryBuilder->expr()->in('be_groups.uid', GeneralUtility::intExplode(',', $groupList, true)))
210 ->groupBy('sys_action.uid');
211 }
212
213 $queryResult = $queryBuilder->execute();
214 while ($actionRow = $queryResult->fetch()) {
215 $editActionLink = '';
216
217 // Admins are allowed to edit sys_action records
218 if ($this->getBackendUser()->isAdmin()) {
219 $uidEditArgument = 'edit[sys_action][' . (int)$actionRow['uid'] . ']';
220
221 $link = BackendUtility::getModuleUrl(
222 'record_edit',
223 [
224 $uidEditArgument => 'edit',
225 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
226 ]
227 );
228
229 $title = 'title="' . $this->getLanguageService()->getLL('edit-sys_action') . '"';
230 $icon = $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render();
231 $editActionLink = '<a class="btn btn-default btn-sm" href="' . $link . '"' . $title . '>';
232 $editActionLink .= $icon . ' ' . $this->getLanguageService()->getLL('edit-sys_action') . '</a>';
233 }
234
235 $actionList[] = [
236 'uid' => 'actiontask' . $actionRow['uid'],
237 'title' => $actionRow['title'],
238 'description' => $actionRow['description'],
239 'descriptionHtml' => (
240 $actionRow['description']
241 ? '<p>' . nl2br(htmlspecialchars($actionRow['description'])) . '</p>'
242 : ''
243 ) . $editActionLink,
244 'link' => $this->moduleUrl
245 . '&SET[function]=sys_action.'
246 . self::class
247 . '&show='
248 . (int)$actionRow['uid']
249 ];
250 }
251
252 return $actionList;
253 }
254
255 /**
256 * Render the menu of sys_actions
257 *
258 * @return string List of sys_actions as HTML
259 */
260 protected function renderActionList()
261 {
262 $content = '';
263 // Get the sys_action records
264 $actionList = $this->getActions();
265 // If any actions are found for the current users
266 if (!empty($actionList)) {
267 $content .= $this->taskObject->renderListMenu($actionList);
268 } else {
269 $this->addMessage(
270 $this->getLanguageService()->getLL('action_not-found-description'),
271 $this->getLanguageService()->getLL('action_not-found'),
272 FlashMessage::INFO
273 );
274 }
275 // Admin users can create a new action
276 if ($this->getBackendUser()->isAdmin()) {
277 $link = BackendUtility::getModuleUrl(
278 'record_edit',
279 [
280 'edit[sys_action][0]' => 'new',
281 'returnUrl' => $this->moduleUrl
282 ]
283 );
284
285 $content .= '<p>' .
286 '<a class="btn btn-default" href="' . $link . '" title="' . $this->getLanguageService()->getLL('new-sys_action') . '">' .
287 $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . ' ' .
288 $this->getLanguageService()->getLL('new-sys_action') .
289 '</a></p>';
290 }
291 return $content;
292 }
293
294 /**
295 * Action to create a new BE user
296 *
297 * @param array $record sys_action record
298 * @return string form to create a new user
299 */
300 protected function viewNewBackendUser($record)
301 {
302 $content = '';
303 $beRec = BackendUtility::getRecord('be_users', (int)$record['t1_copy_of_user']);
304 // A record is need which is used as copy for the new user
305 if (!is_array($beRec)) {
306 $this->addMessage(
307 $this->getLanguageService()->getLL('action_notReady'),
308 $this->getLanguageService()->getLL('action_error'),
309 FlashMessage::ERROR
310 );
311 $content .= $this->renderFlashMessages();
312 return $content;
313 }
314 $vars = GeneralUtility::_POST('data');
315 $key = 'NEW';
316 if ($vars['sent'] == 1) {
317 $errors = [];
318 // Basic error checks
319 if (!empty($vars['email']) && !GeneralUtility::validEmail($vars['email'])) {
320 $errors[] = $this->getLanguageService()->getLL('error-wrong-email');
321 }
322 if (empty($vars['username'])) {
323 $errors[] = $this->getLanguageService()->getLL('error-username-empty');
324 }
325 if ($vars['key'] === 'NEW' && empty($vars['password'])) {
326 $errors[] = $this->getLanguageService()->getLL('error-password-empty');
327 }
328 if ($vars['key'] !== 'NEW' && !$this->isCreatedByUser($vars['key'], $record)) {
329 $errors[] = $this->getLanguageService()->getLL('error-wrong-user');
330 }
331 foreach ($this->hookObjects as $hookObject) {
332 if (method_exists($hookObject, 'viewNewBackendUser_Error')) {
333 $errors = $hookObject->viewNewBackendUser_Error($vars, $errors, $this);
334 }
335 }
336 // Show errors if there are any
337 if (!empty($errors)) {
338 $this->addMessage(
339 implode(LF, $errors),
340 $this->getLanguageService()->getLL('action_error'),
341 FlashMessage::ERROR
342 );
343 } else {
344 // Save user
345 $key = $this->saveNewBackendUser($record, $vars);
346 // Success message
347 $message = $vars['key'] === 'NEW'
348 ? $this->getLanguageService()->getLL('success-user-created')
349 : $this->getLanguageService()->getLL('success-user-updated');
350 $this->addMessage(
351 $message,
352 $this->getLanguageService()->getLL('success')
353 );
354 }
355 $content .= $this->renderFlashMessages();
356 }
357 // Load BE user to edit
358 if ((int)GeneralUtility::_GP('be_users_uid') > 0) {
359 $tmpUserId = (int)GeneralUtility::_GP('be_users_uid');
360 // Check if the selected user is created by the current user
361 $rawRecord = $this->isCreatedByUser($tmpUserId, $record);
362 if ($rawRecord) {
363 // Delete user
364 if (GeneralUtility::_GP('delete') == 1) {
365 $this->deleteUser($tmpUserId, $record['uid']);
366 }
367 $key = $tmpUserId;
368 $vars = $rawRecord;
369 }
370 }
371 $content .= '<form action="" class="panel panel-default" method="post" enctype="multipart/form-data">
372 <fieldset class="form-section">
373 <h4 class="form-section-headline">' . $this->getLanguageService()->getLL('action_t1_legend_generalFields') . '</h4>
374 <div class="form-group">
375 <label for="field_disable">' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_general.xlf:LGL.disable') . '</label>
376 <input type="checkbox" id="field_disable" name="data[disable]" value="1" class="checkbox" ' . ($vars['disable'] == 1 ? ' checked="checked" ' : '') . ' />
377 </div>
378 <div class="form-group">
379 <label for="field_realname">' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_general.xlf:LGL.name') . '</label>
380 <input type="text" id="field_realname" class="form-control" name="data[realName]" value="' . htmlspecialchars($vars['realName']) . '" />
381 </div>
382 <div class="form-group">
383 <label for="field_username">' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_tca.xlf:be_users.username') . '</label>
384 <input type="text" id="field_username" class="form-control" name="data[username]" value="' . htmlspecialchars($vars['username']) . '" />
385 </div>
386 <div class="form-group">
387 <label for="field_password">' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_tca.xlf:be_users.password') . '</label>
388 <input type="password" id="field_password" class="form-control" name="data[password]" value="" />
389 </div>
390 <div class="form-group">
391 <label for="field_email">' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_general.xlf:LGL.email') . '</label>
392 <input type="text" id="field_email" class="form-control" name="data[email]" value="' . htmlspecialchars($vars['email']) . '" />
393 </div>
394 </fieldset>
395 <fieldset class="form-section">
396 <h4 class="form-section-headline">' . $this->getLanguageService()->getLL('action_t1_legend_configuration') . '</h4>
397 <div class="form-group">
398 <label for="field_usergroup">' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_tca.xlf:be_users.usergroup') . '</label>
399 <select id="field_usergroup" class="form-control" name="data[usergroup][]" multiple="multiple">
400 ' . $this->getUsergroups($record, $vars) . '
401 </select>
402 </div>
403 <div class="form-group">
404 <input type="hidden" name="data[key]" value="' . $key . '" />
405 <input type="hidden" name="data[sent]" value="1" />
406 <input class="btn btn-default" type="submit" value="' . ($key === 'NEW' ? $this->getLanguageService()->getLL('action_Create') : $this->getLanguageService()->getLL('action_Update')) . '" />
407 </div>
408 </fieldset>
409 </form>';
410 $content .= $this->getCreatedUsers($record, $key);
411 return $content;
412 }
413
414 /**
415 * Delete a BE user and redirect to the action by its id
416 *
417 * @param int $userId Id of the BE user
418 * @param int $actionId Id of the action
419 * @return void
420 */
421 protected function deleteUser($userId, $actionId)
422 {
423 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users')->update(
424 'be_users',
425 ['deleted' => 1, 'tstamp' => (int)$GLOBALS['ACCESS_TIME']],
426 ['uid' => (int)$userId]
427 );
428
429 // redirect to the original task
430 HttpUtility::redirect($this->moduleUrl . '&show=' . (int)$actionId);
431 }
432
433 /**
434 * Check if a BE user is created by the current user
435 *
436 * @param int $id Id of the BE user
437 * @param array $action sys_action record.
438 * @return mixed The record of the BE user if found, otherwise FALSE
439 */
440 protected function isCreatedByUser($id, $action)
441 {
442 $record = BackendUtility::getRecord('be_users', $id, '*', ' AND cruser_id=' . $this->getBackendUser()->user['uid'] . ' AND createdByAction=' . $action['uid']);
443 if (is_array($record)) {
444 return $record;
445 } else {
446 return false;
447 }
448 }
449
450 /**
451 * Render all users who are created by the current BE user including a link to edit the record
452 *
453 * @param array $action sys_action record.
454 * @param int $selectedUser Id of a selected user
455 * @return string html list of users
456 */
457 protected function getCreatedUsers($action, $selectedUser)
458 {
459 $content = '';
460 $userList = [];
461
462 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
463 ->getQueryBuilderForTable('be_users');
464
465 $queryBuilder->getRestrictions()
466 ->removeAll()
467 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
468
469 $res = $queryBuilder
470 ->select('*')
471 ->from('be_users')
472 ->where(
473 $queryBuilder->expr()->eq('cruser_id', (int)$this->getBackendUser()->user['uid']),
474 $queryBuilder->expr()->eq('createdByAction', (int)$action['uid'])
475 )
476 ->orderBy('username')
477 ->execute();
478
479 // Render the user records
480 while ($row = $res->fetch()) {
481 $icon = '<span title="' . htmlspecialchars('uid=' . $row['uid']) . '">' . $this->iconFactory->getIconForRecord('be_users', $row, Icon::SIZE_SMALL)->render() . '</span>';
482 $line = $icon . $this->action_linkUserName($row['username'], $row['realName'], $action['uid'], $row['uid']);
483 // Selected user
484 if ($row['uid'] == $selectedUser) {
485 $line = '<strong>' . $line . '</strong>';
486 }
487 $userList[] = '<li class="list-group-item">' . $line . '</li>';
488 }
489
490 // If any records found
491 if (!empty($userList)) {
492 $content .= '<div class="panel panel-default">';
493 $content .= '<div class="panel-heading">';
494 $content .= '<h3 class="panel-title">' . htmlspecialchars($this->getLanguageService()->getLL('action_t1_listOfUsers')) . '</h3>';
495 $content .= '</div>';
496 $content .= '<ul class="list-group">' . implode($userList) . '</ul>';
497 $content .= '</div>';
498 }
499 return $content;
500 }
501
502 /**
503 * Create a link to edit a user
504 *
505 * @param string $username Username
506 * @param string $realName Real name of the user
507 * @param int $sysActionUid Id of the sys_action record
508 * @param int $userId Id of the user
509 * @return string html link
510 */
511 protected function action_linkUserName($username, $realName, $sysActionUid, $userId)
512 {
513 if (!empty($realName)) {
514 $username .= ' (' . $realName . ')';
515 }
516 // Link to update the user record
517 $href = $this->moduleUrl . '&SET[function]=sys_action.TYPO3\\CMS\\SysAction\\ActionTask&show=' . (int)$sysActionUid . '&be_users_uid=' . (int)$userId;
518 $link = '<a href="' . htmlspecialchars($href) . '">' . htmlspecialchars($username) . '</a>';
519 // Link to delete the user record
520 $link .= '
521 <a href="' . htmlspecialchars(($href . '&delete=1')) . '" class="t3js-confirm-trigger" data-title="' . htmlspecialchars($this->getLanguageService()->getLL('lDelete_warning_title')) . '" data-message="' . htmlspecialchars($this->getLanguageService()->getLL('lDelete_warning')) . '">'
522 . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() .
523 '</a>';
524 return $link;
525 }
526
527 /**
528 * Save/Update a BE user
529 *
530 * @param array $record Current action record
531 * @param array $vars POST vars
532 * @return int Id of the new/updated user
533 */
534 protected function saveNewBackendUser($record, $vars)
535 {
536 // Check if the db mount is a page the current user is allowed to.);
537 $vars['db_mountpoints'] = $this->fixDbMount($vars['db_mountpoints']);
538 // Check if the usergroup is allowed
539 $vars['usergroup'] = $this->fixUserGroup($vars['usergroup'], $record);
540 $key = $vars['key'];
541 $vars['password'] = trim($vars['password']);
542 // Check if md5 is used as password encryption
543 if ($vars['password'] !== '' && strpos($GLOBALS['TCA']['be_users']['columns']['password']['config']['eval'], 'md5') !== false) {
544 $vars['password'] = md5($vars['password']);
545 }
546 $data = '';
547 $newUserId = 0;
548 if ($key === 'NEW') {
549 $beRec = BackendUtility::getRecord('be_users', (int)$record['t1_copy_of_user']);
550 if (is_array($beRec)) {
551 $data = [];
552 $data['be_users'][$key] = $beRec;
553 $data['be_users'][$key]['username'] = $this->fixUsername($vars['username'], $record['t1_userprefix']);
554 $data['be_users'][$key]['password'] = $vars['password'];
555 $data['be_users'][$key]['realName'] = $vars['realName'];
556 $data['be_users'][$key]['email'] = $vars['email'];
557 $data['be_users'][$key]['disable'] = (int)$vars['disable'];
558 $data['be_users'][$key]['admin'] = 0;
559 $data['be_users'][$key]['usergroup'] = $vars['usergroup'];
560 $data['be_users'][$key]['db_mountpoints'] = $vars['db_mountpoints'];
561 $data['be_users'][$key]['createdByAction'] = $record['uid'];
562 }
563 } else {
564 // Check ownership
565 $beRec = BackendUtility::getRecord('be_users', (int)$key);
566 if (is_array($beRec) && $beRec['cruser_id'] == $this->getBackendUser()->user['uid']) {
567 $data = [];
568 $data['be_users'][$key]['username'] = $this->fixUsername($vars['username'], $record['t1_userprefix']);
569 if ($vars['password'] !== '') {
570 $data['be_users'][$key]['password'] = $vars['password'];
571 }
572 $data['be_users'][$key]['realName'] = $vars['realName'];
573 $data['be_users'][$key]['email'] = $vars['email'];
574 $data['be_users'][$key]['disable'] = (int)$vars['disable'];
575 $data['be_users'][$key]['admin'] = 0;
576 $data['be_users'][$key]['usergroup'] = $vars['usergroup'];
577 $data['be_users'][$key]['db_mountpoints'] = $vars['db_mountpoints'];
578 $newUserId = $key;
579 }
580 }
581 // Save/update user by using TCEmain
582 if (is_array($data)) {
583 $tce = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
584 $tce->start($data, [], $this->getBackendUser());
585 $tce->admin = 1;
586 $tce->process_datamap();
587 $newUserId = (int)$tce->substNEWwithIDs['NEW'];
588 if ($newUserId) {
589 // Create
590 $this->action_createDir($newUserId);
591 } else {
592 // Update
593 $newUserId = (int)$key;
594 }
595 unset($tce);
596 }
597 return $newUserId;
598 }
599
600 /**
601 * Create the username based on the given username and the prefix
602 *
603 * @param string $username Username
604 * @param string $prefix Prefix
605 * @return string Combined username
606 */
607 protected function fixUsername($username, $prefix)
608 {
609 $prefix = trim($prefix);
610 if (substr($username, 0, strlen($prefix)) === $prefix) {
611 $username = substr($username, strlen($prefix));
612 }
613 return $prefix . $username;
614 }
615
616 /**
617 * Clean the to be applied usergroups from not allowed ones
618 *
619 * @param array $appliedUsergroups Array of to be applied user groups
620 * @param array $actionRecord The action record
621 * @return array Cleaned array
622 */
623 protected function fixUserGroup($appliedUsergroups, $actionRecord)
624 {
625 if (is_array($appliedUsergroups)) {
626 $cleanGroupList = [];
627 // Create an array from the allowed usergroups using the uid as key
628 $allowedUsergroups = array_flip(explode(',', $actionRecord['t1_allowed_groups']));
629 // Walk through the array and check every uid if it is under the allowed ines
630 foreach ($appliedUsergroups as $group) {
631 if (isset($allowedUsergroups[$group])) {
632 $cleanGroupList[] = $group;
633 }
634 }
635 $appliedUsergroups = $cleanGroupList;
636 }
637 return $appliedUsergroups;
638 }
639
640 /**
641 * Clean the to be applied DB-Mounts from not allowed ones
642 *
643 * @param string $appliedDbMounts List of pages like pages_123,pages456
644 * @return string Cleaned list
645 */
646 protected function fixDbMount($appliedDbMounts)
647 {
648 // Admins can see any page, no need to check there
649 if (!empty($appliedDbMounts) && !$this->getBackendUser()->isAdmin()) {
650 $cleanDbMountList = [];
651 $dbMounts = GeneralUtility::trimExplode(',', $appliedDbMounts, true);
652 // Walk through every wanted DB-Mount and check if it allowed for the current user
653 foreach ($dbMounts as $dbMount) {
654 $uid = (int)substr($dbMount, strrpos($dbMount, '_') + 1);
655 $page = BackendUtility::getRecord('pages', $uid);
656 // Check rootline and access rights
657 if ($this->checkRootline($uid) && $this->getBackendUser()->calcPerms($page)) {
658 $cleanDbMountList[] = 'pages_' . $uid;
659 }
660 }
661 // Build the clean list
662 $appliedDbMounts = implode(',', $cleanDbMountList);
663 }
664 return $appliedDbMounts;
665 }
666
667 /**
668 * Check if a page is inside the rootline the current user can see
669 *
670 * @param int $pageId Id of the the page to be checked
671 * @return bool Access to the page
672 */
673 protected function checkRootline($pageId)
674 {
675 $access = false;
676 $dbMounts = array_flip(explode(',', trim($this->getBackendUser()->dataLists['webmount_list'], ',')));
677 $rootline = BackendUtility::BEgetRootLine($pageId);
678 foreach ($rootline as $page) {
679 if (isset($dbMounts[$page['uid']]) && !$access) {
680 $access = true;
681 }
682 }
683 return $access;
684 }
685
686 /**
687 * Create a user directory if defined
688 *
689 * @param int $uid Id of the user record
690 * @return void
691 */
692 protected function action_createDir($uid)
693 {
694 $path = $this->action_getUserMainDir();
695 if ($path) {
696 GeneralUtility::mkdir($path . $uid);
697 GeneralUtility::mkdir($path . $uid . '/_temp_/');
698 }
699 }
700
701 /**
702 * Get the path to the user home directory which is set in the localconf.php
703 *
704 * @return string Path
705 */
706 protected function action_getUserMainDir()
707 {
708 $path = $GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath'];
709 // If path is set and a valid directory
710 if ($path && @is_dir($path) && $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] && GeneralUtility::isFirstPartOfStr($path, $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']) && substr($path, -1) == '/') {
711 return $path;
712 }
713 }
714
715 /**
716 * Get all allowed usergroups which can be applied to a user record
717 *
718 * @param array $record sys_action record
719 * @param array $vars Selected be_user record
720 * @return string Rendered user groups
721 */
722 protected function getUsergroups($record, $vars)
723 {
724 $content = '';
725 // Do nothing if no groups are allowed
726 if (empty($record['t1_allowed_groups'])) {
727 return $content;
728 }
729 $content .= '<option value=""></option>';
730 $grList = GeneralUtility::trimExplode(',', $record['t1_allowed_groups'], true);
731 foreach ($grList as $group) {
732 $checkGroup = BackendUtility::getRecord('be_groups', $group);
733 if (is_array($checkGroup)) {
734 $selected = GeneralUtility::inList($vars['usergroup'], $checkGroup['uid']) ? ' selected="selected" ' : '';
735 $content .= '<option ' . $selected . 'value="' . $checkGroup['uid'] . '">' . htmlspecialchars($checkGroup['title']) . '</option>';
736 }
737 }
738 return $content;
739 }
740
741 /**
742 * Action to create a new record
743 *
744 * @param array $record sys_action record
745 * @return void Redirect to form to create a record
746 */
747 protected function viewNewRecord($record)
748 {
749 $link = BackendUtility::getModuleUrl(
750 'record_edit',
751 [
752 'edit[' . $record['t3_tables'] . '][' . (int)$record['t3_listPid'] . ']' => 'new',
753 'returnUrl' => $this->moduleUrl
754 ]
755 );
756 HttpUtility::redirect($link);
757 }
758
759 /**
760 * Action to edit records
761 *
762 * @param array $record sys_action record
763 * @return string list of records
764 */
765 protected function viewEditRecord($record)
766 {
767 $content = '';
768 $actionList = [];
769 $dbAnalysis = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
770 $dbAnalysis->setFetchAllFields(true);
771 $dbAnalysis->start($record['t4_recordsToEdit'], '*');
772 $dbAnalysis->getFromDB();
773 // collect the records
774 foreach ($dbAnalysis->itemArray as $el) {
775 $path = BackendUtility::getRecordPath($el['id'], $this->taskObject->perms_clause, $this->getBackendUser()->uc['titleLen']);
776 $record = BackendUtility::getRecord($el['table'], $dbAnalysis->results[$el['table']][$el['id']]);
777 $title = BackendUtility::getRecordTitle($el['table'], $dbAnalysis->results[$el['table']][$el['id']]);
778 $description = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$el['table']]['ctrl']['title']));
779 // @todo: which information could be needful
780 if (isset($record['crdate'])) {
781 $description .= ' - ' . BackendUtility::dateTimeAge($record['crdate']);
782 }
783 $link = BackendUtility::getModuleUrl(
784 'record_edit',
785 [
786 'edit[' . $el['table'] . '][' . $el['id'] . ']' => 'edit',
787 'returnUrl' => $this->moduleUrl
788 ]
789 );
790 $actionList[$el['id']] = [
791 'uid' => 'record-' . $el['table'] . '-' . $el['id'],
792 'title' => $title,
793 'description' => BackendUtility::getRecordTitle($el['table'], $dbAnalysis->results[$el['table']][$el['id']]),
794 'descriptionHtml' => $description,
795 'link' => $link,
796 'icon' => '<span title="' . htmlspecialchars($path) . '">' . $this->iconFactory->getIconForRecord($el['table'], $dbAnalysis->results[$el['table']][$el['id']], Icon::SIZE_SMALL)->render() . '</span>'
797 ];
798 }
799 // Render the record list
800 $content .= $this->taskObject->renderListMenu($actionList);
801 return $content;
802 }
803
804 /**
805 * Action to view the result of a SQL query
806 *
807 * @param array $record sys_action record
808 * @return string Result of the query
809 */
810 protected function viewSqlQuery($record)
811 {
812 $content = '';
813 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('lowlevel')) {
814 $sql_query = unserialize($record['t2_data']);
815 if (!is_array($sql_query) || is_array($sql_query) && strtoupper(substr(trim($sql_query['qSelect']), 0, 6)) === 'SELECT') {
816 $actionContent = '';
817 $fullsearch = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\QueryView::class);
818 $fullsearch->formW = 40;
819 $fullsearch->noDownloadB = 1;
820 $type = $sql_query['qC']['search_query_makeQuery'];
821 if ($sql_query['qC']['labels_noprefix'] === 'on') {
822 $GLOBALS['SOBE']->MOD_SETTINGS['labels_noprefix'] = 'on';
823 }
824 $sqlQuery = $sql_query['qSelect'];
825 $queryIsEmpty = false;
826 if ($sqlQuery) {
827 try {
828 $dataRows = GeneralUtility::makeInstance(ConnectionPool::class)
829 ->getConnectionForTable($sql_query['qC']['queryTable'])
830 ->executeQuery($sqlQuery)->fetchAll();
831 $fullsearch->formW = 48;
832 // Additional configuration
833 $GLOBALS['SOBE']->MOD_SETTINGS['search_result_labels'] = 1;
834 $GLOBALS['SOBE']->MOD_SETTINGS['queryFields'] = $sql_query['qC']['queryFields'];
835 $cP = $fullsearch->getQueryResultCode($type, $dataRows, $sql_query['qC']['queryTable']);
836 $actionContent = $cP['content'];
837 // If the result is rendered as csv or xml, show a download link
838 if ($type === 'csv' || $type === 'xml') {
839 $actionContent .= '<a href="' . GeneralUtility::getIndpEnv('REQUEST_URI') . '&download_file=1"><strong>' . $this->getLanguageService()->getLL('action_download_file') . '</strong></a>';
840 }
841 } catch (DBALException $e) {
842 $actionContent .= $e->getMessage();
843 }
844 } else {
845 // Query is empty (not built)
846 $queryIsEmpty = true;
847 $this->addMessage(
848 $this->getLanguageService()->getLL('action_emptyQuery'),
849 $this->getLanguageService()->getLL('action_error'),
850 FlashMessage::ERROR
851 );
852 $content .= $this->renderFlashMessages();
853 }
854 // Admin users are allowed to see and edit the query
855 if ($this->getBackendUser()->isAdmin()) {
856 if (!$queryIsEmpty) {
857 $actionContent .= '<div class="panel panel-default"><div class="panel-body"><pre>' . $sql_query['qSelect'] . '</pre></div></div>';
858 }
859 $actionContent .= '<a title="' . $this->getLanguageService()->getLL('action_editQuery') . '" class="btn btn-default" href="'
860 . htmlspecialchars(BackendUtility::getModuleUrl('system_dbint')
861 . '&id=' . '&SET[function]=search' . '&SET[search]=query'
862 . '&storeControl[STORE]=-' . $record['uid'] . '&storeControl[LOAD]=1')
863 . '">'
864 . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . ' '
865 . $this->getLanguageService()->getLL(($queryIsEmpty ? 'action_createQuery'
866 : 'action_editQuery')) . '</a>';
867 }
868 $content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('action_t2_result')) . '</h2>' . $actionContent;
869 } else {
870 // Query is not configured
871 $this->addMessage(
872 $this->getLanguageService()->getLL('action_notReady'),
873 $this->getLanguageService()->getLL('action_error'),
874 FlashMessage::ERROR
875 );
876 $content .= $this->renderFlashMessages();
877 }
878 } else {
879 // Required sysext lowlevel is not installed
880 $this->addMessage(
881 $this->getLanguageService()->getLL('action_lowlevelMissing'),
882 $this->getLanguageService()->getLL('action_error'),
883 FlashMessage::ERROR
884 );
885 $content .= $this->renderFlashMessages();
886 }
887 return $content;
888 }
889
890 /**
891 * Action to create a list of records of a specific table and pid
892 *
893 * @param array $record sys_action record
894 * @return string list of records
895 */
896 protected function viewRecordList($record)
897 {
898 $content = '';
899 $this->id = (int)$record['t3_listPid'];
900 $this->table = $record['t3_tables'];
901 if ($this->id == 0) {
902 $this->addMessage(
903 $this->getLanguageService()->getLL('action_notReady'),
904 $this->getLanguageService()->getLL('action_error'),
905 FlashMessage::ERROR
906 );
907 $content .= $this->renderFlashMessages();
908 return $content;
909 }
910 // Loading current page record and checking access:
911 $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->taskObject->perms_clause);
912 $access = is_array($this->pageinfo) ? 1 : 0;
913 // If there is access to the page, then render the list contents and set up the document template object:
914 if ($access) {
915 // Initialize the dblist object:
916 $dblist = GeneralUtility::makeInstance(\TYPO3\CMS\SysAction\ActionList::class);
917 $dblist->script = GeneralUtility::getIndpEnv('REQUEST_URI');
918 $dblist->calcPerms = $this->getBackendUser()->calcPerms($this->pageinfo);
919 $dblist->thumbs = $this->getBackendUser()->uc['thumbnailsByDefault'];
920 $dblist->returnUrl = $this->taskObject->returnUrl;
921 $dblist->allFields = 1;
922 $dblist->localizationView = 1;
923 $dblist->showClipboard = 0;
924 $dblist->disableSingleTableView = 1;
925 $dblist->pageRow = $this->pageinfo;
926 $dblist->counter++;
927 $dblist->MOD_MENU = ['bigControlPanel' => '', 'clipBoard' => '', 'localization' => ''];
928 $dblist->modTSconfig = $this->taskObject->modTSconfig;
929 $dblist->dontShowClipControlPanels = (!$this->taskObject->MOD_SETTINGS['bigControlPanel'] && $dblist->clipObj->current == 'normal' && !$this->modTSconfig['properties']['showClipControlPanelsDespiteOfCMlayers']);
930 // Initialize the listing object, dblist, for rendering the list:
931 $this->pointer = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(GeneralUtility::_GP('pointer'), 0, 100000);
932 $dblist->start($this->id, $this->table, $this->pointer, $this->taskObject->search_field, $this->taskObject->search_levels, $this->taskObject->showLimit);
933 $dblist->setDispFields();
934 // Render the list of tables:
935 $dblist->generateList();
936 // Add JavaScript functions to the page:
937 $this->taskObject->getModuleTemplate()->addJavaScriptCode(
938 'ActionTaskInlineJavascript',
939 '
940
941 function jumpExt(URL,anchor) {
942 var anc = anchor?anchor:"";
943 window.location.href = URL+(T3_THIS_LOCATION?"&returnUrl="+T3_THIS_LOCATION:"")+anc;
944 return false;
945 }
946 function jumpSelf(URL) {
947 window.location.href = URL+(T3_RETURN_URL?"&returnUrl="+T3_RETURN_URL:"");
948 return false;
949 }
950
951 function setHighlight(id) {
952 top.fsMod.recentIds["web"]=id;
953 top.fsMod.navFrameHighlightedID["web"]="pages"+id+"_"+top.fsMod.currentBank; // For highlighting
954
955 if (top.content && top.content.nav_frame && top.content.nav_frame.refresh_nav) {
956 top.content.nav_frame.refresh_nav();
957 }
958 }
959
960 ' . $dblist->CBfunctions() . '
961 function editRecords(table,idList,addParams,CBflag) {
962 window.location.href="' . BackendUtility::getModuleUrl('record_edit', ['returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')]) . '&edit["+table+"]["+idList+"]=edit"+addParams;
963 }
964 function editList(table,idList) {
965 var list="";
966
967 // Checking how many is checked, how many is not
968 var pointer=0;
969 var pos = idList.indexOf(",");
970 while (pos!=-1) {
971 if (cbValue(table+"|"+idList.substr(pointer,pos-pointer))) {
972 list+=idList.substr(pointer,pos-pointer)+",";
973 }
974 pointer=pos+1;
975 pos = idList.indexOf(",",pointer);
976 }
977 if (cbValue(table+"|"+idList.substr(pointer))) {
978 list+=idList.substr(pointer)+",";
979 }
980
981 return list ? list : idList;
982 }
983 T3_THIS_LOCATION = ' . GeneralUtility::quoteJSvalue(rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'))) . ';
984
985 if (top.fsMod) top.fsMod.recentIds["web"] = ' . (int)$this->id . ';
986 '
987 );
988 // Setting up the context sensitive menu:
989 $this->taskObject->getModuleTemplate()->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
990 // Begin to compile the whole page
991 $content .= '<form action="' . htmlspecialchars($dblist->listURL()) . '" method="post" name="dblistForm">' . $dblist->HTMLcode . '<input type="hidden" name="cmd_table" /><input type="hidden" name="cmd" />
992 </form>';
993 // If a listing was produced, create the page footer with search form etc:
994 // Making field select box (when extended view for a single table is enabled):
995 if ($dblist->HTMLcode && $dblist->table) {
996 $content .= $dblist->fieldSelectBox($dblist->table);
997 }
998 } else {
999 // Not enough rights to access the list view or the page
1000 $this->addMessage(
1001 $this->getLanguageService()->getLL('action_error-access'),
1002 $this->getLanguageService()->getLL('action_error'),
1003 FlashMessage::ERROR
1004 );
1005 $content .= $this->renderFlashMessages();
1006 }
1007 return $content;
1008 }
1009
1010 /**
1011 * @param string $message
1012 * @param string $title
1013 * @param int $severity
1014 *
1015 * @throws \TYPO3\CMS\Core\Exception
1016 */
1017 protected function addMessage($message, $title = '', $severity = FlashMessage::OK)
1018 {
1019 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $title, $severity);
1020 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1021 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1022 $defaultFlashMessageQueue->enqueue($flashMessage);
1023 }
1024
1025 /**
1026 * Render all currently enqueued FlashMessages
1027 *
1028 * @return string
1029 */
1030 protected function renderFlashMessages()
1031 {
1032 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1033 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1034 return $defaultFlashMessageQueue->renderFlashMessages();
1035 }
1036
1037 /**
1038 * Returns LanguageService
1039 *
1040 * @return \TYPO3\CMS\Lang\LanguageService
1041 */
1042 protected function getLanguageService()
1043 {
1044 return $GLOBALS['LANG'];
1045 }
1046
1047 /**
1048 * Returns the current BE user.
1049 *
1050 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1051 */
1052 protected function getBackendUser()
1053 {
1054 return $GLOBALS['BE_USER'];
1055 }
1056 }