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