[TASK] Deprecate ExtensionManagementUtility::extRelPath
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / EditDocumentController.php
1 <?php
2 namespace TYPO3\CMS\Backend\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
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
20 use TYPO3\CMS\Backend\Form\FormDataCompiler;
21 use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
22 use TYPO3\CMS\Backend\Form\FormResultCompiler;
23 use TYPO3\CMS\Backend\Form\NodeFactory;
24 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
25 use TYPO3\CMS\Backend\Module\AbstractModule;
26 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
27 use TYPO3\CMS\Backend\Utility\BackendUtility;
28 use TYPO3\CMS\Core\Database\ConnectionPool;
29 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
30 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
31 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
32 use TYPO3\CMS\Core\DataHandling\DataHandler;
33 use TYPO3\CMS\Core\Imaging\Icon;
34 use TYPO3\CMS\Core\Messaging\FlashMessage;
35 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
36 use TYPO3\CMS\Core\Messaging\FlashMessageService;
37 use TYPO3\CMS\Core\Page\PageRenderer;
38 use TYPO3\CMS\Core\Type\Bitmask\Permission;
39 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
40 use TYPO3\CMS\Core\Utility\GeneralUtility;
41 use TYPO3\CMS\Core\Utility\HttpUtility;
42 use TYPO3\CMS\Core\Utility\MathUtility;
43 use TYPO3\CMS\Core\Utility\PathUtility;
44 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
45 use TYPO3\CMS\Frontend\Page\PageRepository;
46
47 /**
48 * Script Class: Drawing the editing form for editing records in TYPO3.
49 * Notice: It does NOT use tce_db.php to submit data to, rather it handles submissions itself
50 */
51 class EditDocumentController extends AbstractModule
52 {
53 /**
54 * GPvar "edit": Is an array looking approx like [tablename][list-of-ids]=command, eg.
55 * "&edit[pages][123]=edit". See \TYPO3\CMS\Backend\Utility\BackendUtility::editOnClick(). Value can be seen
56 * modified internally (converting NEW keyword to id, workspace/versioning etc).
57 *
58 * @var array
59 */
60 public $editconf;
61
62 /**
63 * Commalist of fieldnames to edit. The point is IF you specify this list, only those
64 * fields will be rendered in the form. Otherwise all (available) fields in the record
65 * is shown according to the types configuration in $GLOBALS['TCA']
66 *
67 * @var bool
68 */
69 public $columnsOnly;
70
71 /**
72 * Default values for fields (array with tablenames, fields etc. as keys).
73 * Can be seen modified internally.
74 *
75 * @var array
76 */
77 public $defVals;
78
79 /**
80 * Array of values to force being set (as hidden fields). Will be set as $this->defVals
81 * IF defVals does not exist.
82 *
83 * @var array
84 */
85 public $overrideVals;
86
87 /**
88 * If set, this value will be set in $this->retUrl (which is used quite many places
89 * as the return URL). If not set, "dummy.php" will be set in $this->retUrl
90 *
91 * @var string
92 */
93 public $returnUrl;
94
95 /**
96 * Close-document command. Not really sure of all options...
97 *
98 * @var int
99 */
100 public $closeDoc;
101
102 /**
103 * Quite simply, if this variable is set, then the processing of incoming data will be performed
104 * as if a save-button is pressed. Used in the forms as a hidden field which can be set through
105 * JavaScript if the form is somehow submitted by JavaScript).
106 *
107 * @var bool
108 */
109 public $doSave;
110
111 /**
112 * The data array from which the data comes...
113 *
114 * @var array
115 */
116 public $data;
117
118 /**
119 * @var string
120 */
121 public $cmd;
122
123 /**
124 * @var array
125 */
126 public $mirror;
127
128 /**
129 * Clear-cache cmd.
130 *
131 * @var string
132 */
133 public $cacheCmd;
134
135 /**
136 * Redirect (not used???)
137 *
138 * @var string
139 */
140 public $redirect;
141
142 /**
143 * Boolean: If set, then the GET var "&id=" will be added to the
144 * retUrl string so that the NEW id of something is returned to the script calling the form.
145 *
146 * @var bool
147 */
148 public $returnNewPageId;
149
150 /**
151 * @var string
152 */
153 public $vC;
154
155 /**
156 * update BE_USER->uc
157 *
158 * @var array
159 */
160 public $uc;
161
162 /**
163 * ID for displaying the page in the frontend (used for SAVE/VIEW operations)
164 *
165 * @var int
166 */
167 public $popViewId;
168
169 /**
170 * Additional GET vars for the link, eg. "&L=xxx"
171 *
172 * @var string
173 */
174 public $popViewId_addParams;
175
176 /**
177 * Alternative URL for viewing the frontend pages.
178 *
179 * @var string
180 */
181 public $viewUrl;
182
183 /**
184 * Alternative title for the document handler.
185 *
186 * @var string
187 */
188 public $recTitle;
189
190 /**
191 * If set, then no SAVE/VIEW button is printed
192 *
193 * @var bool
194 */
195 public $noView;
196
197 /**
198 * @var string
199 */
200 public $perms_clause;
201
202 /**
203 * If set, the $this->editconf array is returned to the calling script
204 * (used by wizard_add.php for instance)
205 *
206 * @var bool
207 */
208 public $returnEditConf;
209
210 /**
211 * Workspace used for the editing action.
212 *
213 * @var NULL|int
214 */
215 protected $workspace;
216
217 /**
218 * document template object
219 *
220 * @var \TYPO3\CMS\Backend\Template\DocumentTemplate
221 */
222 public $doc;
223
224 /**
225 * a static HTML template, usually in templates/alt_doc.html
226 *
227 * @var string
228 */
229 public $template;
230
231 /**
232 * Content accumulation
233 *
234 * @var string
235 */
236 public $content;
237
238 /**
239 * Return URL script, processed. This contains the script (if any) that we should
240 * RETURN TO from the FormEngine script IF we press the close button. Thus this
241 * variable is normally passed along from the calling script so we can properly return if needed.
242 *
243 * @var string
244 */
245 public $retUrl;
246
247 /**
248 * Contains the parts of the REQUEST_URI (current url). By parts we mean the result of resolving
249 * REQUEST_URI (current url) by the parse_url() function. The result is an array where eg. "path"
250 * is the script path and "query" is the parameters...
251 *
252 * @var array
253 */
254 public $R_URL_parts;
255
256 /**
257 * Contains the current GET vars array; More specifically this array is the foundation for creating
258 * the R_URI internal var (which becomes the "url of this script" to which we submit the forms etc.)
259 *
260 * @var array
261 */
262 public $R_URL_getvars;
263
264 /**
265 * Set to the URL of this script including variables which is needed to re-display the form. See main()
266 *
267 * @var string
268 */
269 public $R_URI;
270
271 /**
272 * @var array
273 */
274 public $MCONF;
275
276 /**
277 * @var array
278 */
279 public $pageinfo;
280
281 /**
282 * Is loaded with the "title" of the currently "open document" - this is used in the
283 * Document Selector box. (see makeDocSel())
284 *
285 * @var string
286 */
287 public $storeTitle = '';
288
289 /**
290 * Contains an array with key/value pairs of GET parameters needed to reach the
291 * current document displayed - used in the Document Selector box. (see compileStoreDat())
292 *
293 * @var array
294 */
295 public $storeArray;
296
297 /**
298 * Contains storeArray, but imploded into a GET parameter string (see compileStoreDat())
299 *
300 * @var string
301 */
302 public $storeUrl;
303
304 /**
305 * Hashed value of storeURL (see compileStoreDat())
306 *
307 * @var string
308 */
309 public $storeUrlMd5;
310
311 /**
312 * Module session data
313 *
314 * @var array
315 */
316 public $docDat;
317
318 /**
319 * An array of the "open documents" - keys are md5 hashes (see $storeUrlMd5) identifying
320 * the various documents on the GET parameter list needed to open it. The values are
321 * arrays with 0,1,2 keys with information about the document (see compileStoreDat()).
322 * The docHandler variable is stored in the $docDat session data, key "0".
323 *
324 * @var array
325 */
326 public $docHandler;
327
328 /**
329 * Array of the elements to create edit forms for.
330 *
331 * @var array
332 */
333 public $elementsData;
334
335 /**
336 * Pointer to the first element in $elementsData
337 *
338 * @var array
339 */
340 public $firstEl;
341
342 /**
343 * Counter, used to count the number of errors (when users do not have edit permissions)
344 *
345 * @var int
346 */
347 public $errorC;
348
349 /**
350 * Counter, used to count the number of new record forms displayed
351 *
352 * @var int
353 */
354 public $newC;
355
356 /**
357 * Is set to the pid value of the last shown record - thus indicating which page to
358 * show when clicking the SAVE/VIEW button
359 *
360 * @var int
361 */
362 public $viewId;
363
364 /**
365 * Is set to additional parameters (like "&L=xxx") if the record supports it.
366 *
367 * @var string
368 */
369 public $viewId_addParams;
370
371 /**
372 * Module TSconfig, loaded from main() based on the page id value of viewId
373 *
374 * @var array
375 */
376 public $modTSconfig;
377
378 /**
379 * @var FormResultCompiler
380 */
381 protected $formResultCompiler;
382
383 /**
384 * Used internally to disable the storage of the document reference (eg. new records)
385 *
386 * @var bool
387 */
388 public $dontStoreDocumentRef = 0;
389
390 /**
391 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
392 */
393 protected $signalSlotDispatcher;
394
395 /**
396 * Stores information needed to preview the currently saved record
397 *
398 * @var array
399 */
400 protected $previewData = [];
401
402 /**
403 * Constructor
404 */
405 public function __construct()
406 {
407 parent::__construct();
408 $GLOBALS['SOBE'] = $this;
409 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_alt_doc.xlf');
410 }
411
412 /**
413 * Get the SignalSlot dispatcher
414 *
415 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
416 */
417 protected function getSignalSlotDispatcher()
418 {
419 if (!isset($this->signalSlotDispatcher)) {
420 $this->signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
421 }
422 return $this->signalSlotDispatcher;
423 }
424
425 /**
426 * Emits a signal after a function was executed
427 *
428 * @param string $signalName
429 */
430 protected function emitFunctionAfterSignal($signalName)
431 {
432 $this->getSignalSlotDispatcher()->dispatch(__CLASS__, $signalName . 'After', [$this]);
433 }
434
435 /**
436 * First initialization.
437 *
438 * @return void
439 */
440 public function preInit()
441 {
442 if (GeneralUtility::_GP('justLocalized')) {
443 $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
444 }
445 // Setting GPvars:
446 $this->editconf = GeneralUtility::_GP('edit');
447 $this->defVals = GeneralUtility::_GP('defVals');
448 $this->overrideVals = GeneralUtility::_GP('overrideVals');
449 $this->columnsOnly = GeneralUtility::_GP('columnsOnly');
450 $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
451 $this->closeDoc = GeneralUtility::_GP('closeDoc');
452 $this->doSave = GeneralUtility::_GP('doSave');
453 $this->returnEditConf = GeneralUtility::_GP('returnEditConf');
454 $this->localizationMode = GeneralUtility::_GP('localizationMode');
455 $this->workspace = GeneralUtility::_GP('workspace');
456 $this->uc = GeneralUtility::_GP('uc');
457 // Setting override values as default if defVals does not exist.
458 if (!is_array($this->defVals) && is_array($this->overrideVals)) {
459 $this->defVals = $this->overrideVals;
460 }
461 // Setting return URL
462 $this->retUrl = $this->returnUrl ?: BackendUtility::getModuleUrl('dummy');
463 // Fix $this->editconf if versioning applies to any of the records
464 $this->fixWSversioningInEditConf();
465 // Make R_URL (request url) based on input GETvars:
466 $this->R_URL_parts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
467 $this->R_URL_getvars = GeneralUtility::_GET();
468 $this->R_URL_getvars['edit'] = $this->editconf;
469 // MAKE url for storing
470 $this->compileStoreDat();
471 // Get session data for the module:
472 $this->docDat = $this->getBackendUser()->getModuleData('FormEngine', 'ses');
473 $this->docHandler = $this->docDat[0];
474 // If a request for closing the document has been sent, act accordingly:
475 if ($this->closeDoc > 0) {
476 $this->closeDocument($this->closeDoc);
477 }
478 // If NO vars are sent to the script, try to read first document:
479 // Added !is_array($this->editconf) because editConf must not be set either.
480 // Anyways I can't figure out when this situation here will apply...
481 if (is_array($this->R_URL_getvars) && count($this->R_URL_getvars) < 2 && !is_array($this->editconf)) {
482 $this->setDocument($this->docDat[1]);
483 }
484
485 // Sets a temporary workspace, this request is based on
486 if ($this->workspace !== null) {
487 $this->getBackendUser()->setTemporaryWorkspace($this->workspace);
488 }
489
490 $this->emitFunctionAfterSignal(__FUNCTION__);
491 }
492
493 /**
494 * Detects, if a save command has been triggered.
495 *
496 * @return bool TRUE, then save the document (data submitted)
497 */
498 public function doProcessData()
499 {
500 $out = $this->doSave
501 || isset($_POST['_savedok'])
502 || isset($_POST['_saveandclosedok'])
503 || isset($_POST['_savedokview'])
504 || isset($_POST['_savedoknew'])
505 || isset($_POST['_translation_savedok_x'])
506 || isset($_POST['_translation_savedokclear_x']);
507 return $out;
508 }
509
510 /**
511 * Do processing of data, submitting it to TCEmain.
512 *
513 * @return void
514 */
515 public function processData()
516 {
517 $beUser = $this->getBackendUser();
518 // GPvars specifically for processing:
519 $control = GeneralUtility::_GP('control');
520 $this->data = GeneralUtility::_GP('data');
521 $this->cmd = GeneralUtility::_GP('cmd');
522 $this->mirror = GeneralUtility::_GP('mirror');
523 $this->cacheCmd = GeneralUtility::_GP('cacheCmd');
524 $this->redirect = GeneralUtility::_GP('redirect');
525 $this->returnNewPageId = GeneralUtility::_GP('returnNewPageId');
526 $this->vC = GeneralUtility::_GP('vC');
527 // See tce_db.php for relevate options here:
528 // Only options related to $this->data submission are included here.
529 /** @var $tce \TYPO3\CMS\Core\DataHandling\DataHandler */
530 $tce = GeneralUtility::makeInstance(DataHandler::class);
531
532 if (!empty($control)) {
533 $tce->setControl($control);
534 }
535 if (isset($_POST['_translation_savedok_x'])) {
536 $tce->updateModeL10NdiffData = 'FORCE_FFUPD';
537 }
538 if (isset($_POST['_translation_savedokclear_x'])) {
539 $tce->updateModeL10NdiffData = 'FORCE_FFUPD';
540 $tce->updateModeL10NdiffDataClear = true;
541 }
542 // Setting default values specific for the user:
543 $TCAdefaultOverride = $beUser->getTSConfigProp('TCAdefaults');
544 if (is_array($TCAdefaultOverride)) {
545 $tce->setDefaultsFromUserTS($TCAdefaultOverride);
546 }
547 // Setting internal vars:
548 if ($beUser->uc['neverHideAtCopy']) {
549 $tce->neverHideAtCopy = 1;
550 }
551 // Loading TCEmain with data:
552 $tce->start($this->data, $this->cmd);
553 if (is_array($this->mirror)) {
554 $tce->setMirror($this->mirror);
555 }
556 // Checking referer / executing
557 $refInfo = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
558 $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
559 if ($httpHost != $refInfo['host']
560 && $this->vC != $beUser->veriCode()
561 && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']
562 ) {
563 $tce->log(
564 '',
565 0,
566 0,
567 0,
568 1,
569 'Referer host \'%s\' and server host \'%s\' did not match and veriCode was not valid either!',
570 1,
571 [$refInfo['host'], $httpHost]
572 );
573 debug('Error: Referer host did not match with server host.');
574 } else {
575 // Perform the saving operation with TCEmain:
576 $tce->process_uploads($_FILES);
577 $tce->process_datamap();
578 $tce->process_cmdmap();
579 // If pages are being edited, we set an instruction about updating the page tree after this operation.
580 if ($tce->pagetreeNeedsRefresh
581 && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))
582 ) {
583 BackendUtility::setUpdateSignal('updatePageTree');
584 }
585 // If there was saved any new items, load them:
586 if (!empty($tce->substNEWwithIDs_table)) {
587 // save the expanded/collapsed states for new inline records, if any
588 FormEngineUtility::updateInlineView($this->uc, $tce);
589 $newEditConf = [];
590 foreach ($this->editconf as $tableName => $tableCmds) {
591 $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
592 if (!empty($keys)) {
593 foreach ($keys as $key) {
594 $editId = $tce->substNEWwithIDs[$key];
595 // Check if the $editId isn't a child record of an IRRE action
596 if (!(is_array($tce->newRelatedIDs[$tableName])
597 && in_array($editId, $tce->newRelatedIDs[$tableName]))
598 ) {
599 // Translate new id to the workspace version:
600 if ($versionRec = BackendUtility::getWorkspaceVersionOfRecord(
601 $beUser->workspace,
602 $tableName,
603 $editId,
604 'uid'
605 )) {
606 $editId = $versionRec['uid'];
607 }
608 $newEditConf[$tableName][$editId] = 'edit';
609 }
610 // Traverse all new records and forge the content of ->editconf so we can continue to EDIT
611 // these records!
612 if ($tableName == 'pages'
613 && $this->retUrl != BackendUtility::getModuleUrl('dummy')
614 && $this->returnNewPageId
615 ) {
616 $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key];
617 }
618 }
619 } else {
620 $newEditConf[$tableName] = $tableCmds;
621 }
622 }
623 // Resetting editconf if newEditConf has values:
624 if (!empty($newEditConf)) {
625 $this->editconf = $newEditConf;
626 }
627 // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
628 $this->R_URL_getvars['edit'] = $this->editconf;
629 // Unsetting default values since we don't need them anymore.
630 unset($this->R_URL_getvars['defVals']);
631 // Re-compile the store* values since editconf changed...
632 $this->compileStoreDat();
633 }
634 // See if any records was auto-created as new versions?
635 if (!empty($tce->autoVersionIdMap)) {
636 $this->fixWSversioningInEditConf($tce->autoVersionIdMap);
637 }
638 // If a document is saved and a new one is created right after.
639 if (isset($_POST['_savedoknew']) && is_array($this->editconf)) {
640 // Finding the current table:
641 reset($this->editconf);
642 $nTable = key($this->editconf);
643 // Finding the first id, getting the records pid+uid
644 reset($this->editconf[$nTable]);
645 $nUid = key($this->editconf[$nTable]);
646 $recordFields = 'pid,uid';
647 if (!empty($GLOBALS['TCA'][$nTable]['ctrl']['versioningWS'])) {
648 $recordFields .= ',t3ver_oid';
649 }
650 $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
651 // Determine insertion mode ('top' is self-explaining,
652 // otherwise new elements are inserted after one using a negative uid)
653 $insertRecordOnTop = ($this->getNewIconMode($nTable) == 'top');
654 // Setting a blank editconf array for a new record:
655 $this->editconf = [];
656 // Determine related page ID for regular live context
657 if ($nRec['pid'] != -1) {
658 if ($insertRecordOnTop) {
659 $relatedPageId = $nRec['pid'];
660 } else {
661 $relatedPageId = -$nRec['uid'];
662 }
663 // Determine related page ID for workspace context
664 } else {
665 if ($insertRecordOnTop) {
666 // Fetch live version of workspace version since the pid value is always -1 in workspaces
667 $liveRecord = BackendUtility::getRecord($nTable, $nRec['t3ver_oid'], $recordFields);
668 $relatedPageId = $liveRecord['pid'];
669 } else {
670 // Use uid of live version of workspace version
671 $relatedPageId = -$nRec['t3ver_oid'];
672 }
673 }
674 $this->editconf[$nTable][$relatedPageId] = 'new';
675 // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
676 $this->R_URL_getvars['edit'] = $this->editconf;
677 // Re-compile the store* values since editconf changed...
678 $this->compileStoreDat();
679 }
680 // If a preview is requested
681 if (isset($_POST['_savedokview'])) {
682 // Get the first table and id of the data array from DataHandler
683 $table = reset(array_keys($this->data));
684 $id = reset(array_keys($this->data[$table]));
685 if (!MathUtility::canBeInterpretedAsInteger($id)) {
686 $id = $tce->substNEWwithIDs[$id];
687 }
688 // Store this information for later use
689 $this->previewData['table'] = $table;
690 $this->previewData['id'] = $id;
691 }
692 $tce->printLogErrorMessages(isset($_POST['_saveandclosedok']) || isset($_POST['_translation_savedok_x']) ? $this->retUrl : $this->R_URL_parts['path'] . '?' . GeneralUtility::implodeArrayForUrl('', $this->R_URL_getvars));
693 }
694 // || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED
695 // because if not, we just get that element re-listed as new. And we don't want that!
696 if (isset($_POST['_saveandclosedok']) || isset($_POST['_translation_savedok_x']) || $this->closeDoc < 0) {
697 $this->closeDocument(abs($this->closeDoc));
698 }
699 }
700
701 /**
702 * Initialize the normal module operation
703 *
704 * @return void
705 */
706 public function init()
707 {
708 $beUser = $this->getBackendUser();
709 // Setting more GPvars:
710 $this->popViewId = GeneralUtility::_GP('popViewId');
711 $this->popViewId_addParams = GeneralUtility::_GP('popViewId_addParams');
712 $this->viewUrl = GeneralUtility::_GP('viewUrl');
713 $this->recTitle = GeneralUtility::_GP('recTitle');
714 $this->noView = GeneralUtility::_GP('noView');
715 $this->perms_clause = $beUser->getPagePermsClause(1);
716 // Set other internal variables:
717 $this->R_URL_getvars['returnUrl'] = $this->retUrl;
718 $this->R_URI = $this->R_URL_parts['path'] . '?' . ltrim(GeneralUtility::implodeArrayForUrl(
719 '',
720 $this->R_URL_getvars
721 ), '&');
722 // Setting virtual document name
723 $this->MCONF['name'] = 'xMOD_alt_doc.php';
724
725 // Create an instance of the document template object
726 $this->doc = $GLOBALS['TBE_TEMPLATE'];
727 $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
728 $pageRenderer->addInlineLanguageLabelFile('EXT:lang/locallang_alt_doc.xlf');
729 // override the default jumpToUrl
730 $this->moduleTemplate->addJavaScriptCode(
731 'jumpToUrl',
732 '
733 function jumpToUrl(URL,formEl) {
734 if (!TBE_EDITOR.isFormChanged()) {
735 window.location.href = URL;
736 } else if (formEl && formEl.type=="checkbox") {
737 formEl.checked = formEl.checked ? 0 : 1;
738 }
739 }
740 '
741 );
742 $t3Configuration = [];
743
744 if (ExtensionManagementUtility::isLoaded('feedit') && (int)GeneralUtility::_GP('feEdit') === 1) {
745 // We have to load some locallang strings and push them into TYPO3.LLL if this request was
746 // triggered by feedit. Originally, this object is fed by BackendController which is not
747 // called here. This block of code is intended to be removed at a later point again.
748 $lang = $this->getLanguageService();
749 $coreLabels = [
750 'csh_tooltip_loading' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:csh_tooltip_loading')
751 ];
752 $generatedLabels = [];
753 $generatedLabels['core'] = $coreLabels;
754 $code = 'TYPO3.LLL = ' . json_encode($generatedLabels) . ';';
755 $filePath = 'typo3temp/assets/js/backend-' . sha1($code) . '.js';
756 if (!file_exists(PATH_site . $filePath)) {
757 // writeFileToTypo3tempDir() returns NULL on success (please double-read!)
758 $error = GeneralUtility::writeFileToTypo3tempDir(PATH_site . $filePath, $code);
759 if ($error !== null) {
760 throw new \RuntimeException('Locallang JS file could not be written to ' . $filePath . '. Reason: ' . $error, 1446118286);
761 }
762 }
763 $pageRenderer->addJsFile('../' . $filePath);
764 }
765
766 $javascript = '
767 TYPO3.configuration = ' . json_encode($t3Configuration) . ';
768 // Object: TS:
769 // TS object overwrites the object declared in tbe_editor.js
770 function typoSetup() { //
771 this.uniqueID = "";
772 }
773 var TS = new typoSetup();
774
775 // Info view:
776 function launchView(table,uid) { //
777 var thePreviewWindow = window.open(
778 ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('show_item') . '&table=') . ' + encodeURIComponent(table) + "&uid=" + encodeURIComponent(uid),
779 "ShowItem" + TS.uniqueID,
780 "height=300,width=410,status=0,menubar=0,resizable=0,location=0,directories=0,scrollbars=1,toolbar=0"
781 );
782 if (thePreviewWindow && thePreviewWindow.focus) {
783 thePreviewWindow.focus();
784 }
785 }
786 function deleteRecord(table,id,url) { //
787 window.location.href = ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&cmd[') . '+table+"]["+id+"][delete]=1&redirect="+escape(url)+"&vC=' . $beUser->veriCode() . '&prErr=1&uPT=1";
788 }
789 ';
790
791 $previewCode = isset($_POST['_savedokview']) && $this->popViewId ? $this->generatePreviewCode() : '';
792 $this->moduleTemplate->addJavaScriptCode(
793 'PreviewCode',
794 $javascript . $previewCode
795 );
796 // Setting up the context sensitive menu:
797 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
798
799 $this->emitFunctionAfterSignal(__FUNCTION__);
800 }
801
802 /**
803 * @return string
804 */
805 protected function generatePreviewCode()
806 {
807 $table = $this->previewData['table'];
808 $recordId = $this->previewData['id'];
809
810 if ($table === 'pages') {
811 $currentPageId = $recordId;
812 } else {
813 $currentPageId = MathUtility::convertToPositiveInteger($this->popViewId);
814 }
815
816 $pageTsConfig = BackendUtility::getPagesTSconfig($currentPageId);
817 $previewConfiguration = isset($pageTsConfig['TCEMAIN.']['preview.'][$table . '.'])
818 ? $pageTsConfig['TCEMAIN.']['preview.'][$table . '.']
819 : [];
820
821 $recordArray = BackendUtility::getRecord($table, $recordId);
822
823 // find the right preview page id
824 $previewPageId = 0;
825 if (isset($previewConfiguration['previewPageId'])) {
826 $previewPageId = $previewConfiguration['previewPageId'];
827 }
828 // if no preview page was configured
829 if (!$previewPageId) {
830 $rootPageData = null;
831 $rootLine = BackendUtility::BEgetRootLine($currentPageId);
832 $currentPage = reset($rootLine);
833 // Allow all doktypes below 200
834 // This makes custom doktype work as well with opening a frontend page.
835 if ((int)$currentPage['doktype'] <= PageRepository::DOKTYPE_SPACER) {
836 // try the current page
837 $previewPageId = $currentPageId;
838 } else {
839 // or search for the root page
840 foreach ($rootLine as $page) {
841 if ($page['is_siteroot']) {
842 $rootPageData = $page;
843 break;
844 }
845 }
846 $previewPageId = isset($rootPageData)
847 ? (int)$rootPageData['uid']
848 : $currentPageId;
849 }
850 }
851
852 $linkParameters = [
853 'no_cache' => 1,
854 ];
855
856 // language handling
857 $languageField = isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
858 ? $GLOBALS['TCA'][$table]['ctrl']['languageField']
859 : '';
860 if ($languageField && !empty($recordArray[$languageField])) {
861 $l18nPointer = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
862 ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
863 : '';
864 if ($l18nPointer && !empty($recordArray[$l18nPointer])
865 && isset($previewConfiguration['useDefaultLanguageRecord'])
866 && !$previewConfiguration['useDefaultLanguageRecord']
867 ) {
868 // use parent record
869 $recordId = $recordArray[$l18nPointer];
870 }
871 $linkParameters['L'] = $recordArray[$languageField];
872 }
873
874 // map record data to GET parameters
875 if (isset($previewConfiguration['fieldToParameterMap.'])) {
876 foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
877 $value = $recordArray[$field];
878 if ($field === 'uid') {
879 $value = $recordId;
880 }
881 $linkParameters[$parameterName] = $value;
882 }
883 }
884
885 // add/override parameters by configuration
886 if (isset($previewConfiguration['additionalGetParameters.'])) {
887 $additionalGetParameters = [];
888 $this->parseAdditionalGetParameters(
889 $additionalGetParameters,
890 $previewConfiguration['additionalGetParameters.']
891 );
892 $linkParameters = array_replace($linkParameters, $additionalGetParameters);
893 }
894
895 $this->popViewId = $previewPageId;
896 $this->popViewId_addParams = GeneralUtility::implodeArrayForUrl('', $linkParameters, '', false, true);
897
898 $previewPageRootline = BackendUtility::BEgetRootLine($this->popViewId);
899 return '
900 if (window.opener) {
901 '
902 . BackendUtility::viewOnClick(
903 $this->popViewId,
904 '',
905 $previewPageRootline,
906 '',
907 $this->viewUrl,
908 $this->popViewId_addParams,
909 false
910 )
911 . '
912 } else {
913 '
914 . BackendUtility::viewOnClick(
915 $this->popViewId,
916 '',
917 $previewPageRootline,
918 '',
919 $this->viewUrl,
920 $this->popViewId_addParams
921 )
922 . '
923 }';
924 }
925
926 /**
927 * Migrates a set of (possibly nested) GET parameters in TypoScript syntax to a plain array
928 *
929 * This basically removes the trailing dots of sub-array keys in TypoScript.
930 * The result can be used to create a query string with GeneralUtility::implodeArrayForUrl().
931 *
932 * @param array $parameters Should be an empty array by default
933 * @param array $typoScript The TypoScript configuration
934 */
935 protected function parseAdditionalGetParameters(array &$parameters, array $typoScript)
936 {
937 foreach ($typoScript as $key => $value) {
938 if (is_array($value)) {
939 $key = rtrim($key, '.');
940 $parameters[$key] = [];
941 $this->parseAdditionalGetParameters($parameters[$key], $value);
942 } else {
943 $parameters[$key] = $value;
944 }
945 }
946 }
947
948 /**
949 * Main module operation
950 *
951 * @return void
952 */
953 public function main()
954 {
955 $body = '';
956 // Begin edit:
957 if (is_array($this->editconf)) {
958 /** @var FormResultCompiler formResultCompiler */
959 $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
960
961 // Creating the editing form, wrap it with buttons, document selector etc.
962 $editForm = $this->makeEditForm();
963 if ($editForm) {
964 $this->firstEl = reset($this->elementsData);
965 // Checking if the currently open document is stored in the list of "open documents" - if not, add it:
966 if (($this->docDat[1] !== $this->storeUrlMd5
967 || !isset($this->docHandler[$this->storeUrlMd5]))
968 && !$this->dontStoreDocumentRef
969 ) {
970 $this->docHandler[$this->storeUrlMd5] = [
971 $this->storeTitle,
972 $this->storeArray,
973 $this->storeUrl,
974 $this->firstEl
975 ];
976 $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->storeUrlMd5]);
977 BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
978 }
979 // Module configuration
980 $this->modTSconfig = $this->viewId ? BackendUtility::getModTSconfig(
981 $this->viewId,
982 'mod.xMOD_alt_doc'
983 ) : [];
984 $body = $this->formResultCompiler->addCssFiles();
985 $body .= $this->compileForm($editForm);
986 $body .= $this->formResultCompiler->printNeededJSFunctions();
987 $body .= '</form>';
988 }
989 }
990 // Access check...
991 // The page will show only if there is a valid page and if this page may be viewed by the user
992 $this->pageinfo = BackendUtility::readPageAccess($this->viewId, $this->perms_clause);
993 if ($this->pageinfo) {
994 $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
995 }
996 // Setting up the buttons and markers for docheader
997 $this->getButtons();
998 $this->languageSwitch($this->firstEl['table'], $this->firstEl['uid'], $this->firstEl['pid']);
999 $this->moduleTemplate->setContent($body);
1000 }
1001
1002 /***************************
1003 *
1004 * Sub-content functions, rendering specific parts of the module content.
1005 *
1006 ***************************/
1007 /**
1008 * Creates the editing form with FormEnigne, based on the input from GPvars.
1009 *
1010 * @return string HTML form elements wrapped in tables
1011 */
1012 public function makeEditForm()
1013 {
1014 // Initialize variables:
1015 $this->elementsData = [];
1016 $this->errorC = 0;
1017 $this->newC = 0;
1018 $editForm = '';
1019 $trData = null;
1020 $beUser = $this->getBackendUser();
1021 // Traverse the GPvar edit array
1022 // Tables:
1023 foreach ($this->editconf as $table => $conf) {
1024 if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
1025 // Traverse the keys/comments of each table (keys can be a commalist of uids)
1026 foreach ($conf as $cKey => $command) {
1027 if ($command == 'edit' || $command == 'new') {
1028 // Get the ids:
1029 $ids = GeneralUtility::trimExplode(',', $cKey, true);
1030 // Traverse the ids:
1031 foreach ($ids as $theUid) {
1032 // Don't save this document title in the document selector if the document is new.
1033 if ($command === 'new') {
1034 $this->dontStoreDocumentRef = 1;
1035 }
1036
1037 /** @var TcaDatabaseRecord $formDataGroup */
1038 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
1039 /** @var FormDataCompiler $formDataCompiler */
1040 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
1041 /** @var NodeFactory $nodeFactory */
1042 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
1043
1044 try {
1045 // Reset viewId - it should hold data of last entry only
1046 $this->viewId = 0;
1047 $this->viewId_addParams = '';
1048
1049 $formDataCompilerInput = [
1050 'tableName' => $table,
1051 'vanillaUid' => (int)$theUid,
1052 'command' => $command,
1053 'returnUrl' => $this->R_URI,
1054 ];
1055 if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
1056 $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
1057 }
1058
1059 $formData = $formDataCompiler->compile($formDataCompilerInput);
1060
1061 // Set this->viewId if possible
1062 if ($command === 'new'
1063 && $table !== 'pages'
1064 && !empty($formData['parentPageRow']['uid'])
1065 ) {
1066 $this->viewId = $formData['parentPageRow']['uid'];
1067 } else {
1068 if ($table == 'pages') {
1069 $this->viewId = $formData['databaseRow']['uid'];
1070 } elseif (!empty($formData['parentPageRow']['uid'])) {
1071 $this->viewId = $formData['parentPageRow']['uid'];
1072 // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
1073 if (!empty($formData['processedTca']['ctrl']['languageField'])
1074 && is_array($formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']])
1075 && $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0] > 0
1076 ) {
1077 $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0];
1078 }
1079 }
1080 }
1081
1082 // Determine if delete button can be shown
1083 $deleteAccess = false;
1084 if ($command === 'edit') {
1085 $permission = $formData['userPermissionOnPage'];
1086 if ($formData['tableName'] === 'pages') {
1087 $deleteAccess = $permission & Permission::PAGE_DELETE ? true : false;
1088 } else {
1089 $deleteAccess = $permission & Permission::CONTENT_EDIT ? true : false;
1090 }
1091 }
1092
1093 // Display "is-locked" message:
1094 if ($command === 'edit') {
1095 $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
1096 if ($lockInfo) {
1097 /** @var $flashMessage \TYPO3\CMS\Core\Messaging\FlashMessage */
1098 $flashMessage = GeneralUtility::makeInstance(
1099 FlashMessage::class,
1100 $lockInfo['msg'],
1101 '',
1102 FlashMessage::WARNING
1103 );
1104 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
1105 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1106 /** @var $defaultFlashMessageQueue FlashMessageQueue */
1107 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1108 $defaultFlashMessageQueue->enqueue($flashMessage);
1109 }
1110 }
1111
1112 // Record title
1113 if (!$this->storeTitle) {
1114 $this->storeTitle = $this->recTitle
1115 ? htmlspecialchars($this->recTitle)
1116 : BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($formData['databaseRow']), true);
1117 }
1118
1119 $this->elementsData[] = [
1120 'table' => $table,
1121 'uid' => $formData['databaseRow']['uid'],
1122 'pid' => $formData['databaseRow']['pid'],
1123 'cmd' => $command,
1124 'deleteAccess' => $deleteAccess
1125 ];
1126
1127 if ($command !== 'new') {
1128 BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
1129 }
1130
1131 // Set list if only specific fields should be rendered. This will trigger
1132 // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1133 if ($this->columnsOnly) {
1134 if (is_array($this->columnsOnly)) {
1135 $formData['fieldListToRender'] = $this->columnsOnly[$table];
1136 } else {
1137 $formData['fieldListToRender'] = $this->columnsOnly;
1138 }
1139 }
1140
1141 $formData['renderType'] = 'outerWrapContainer';
1142 $formResult = $nodeFactory->create($formData)->render();
1143
1144 $html = $formResult['html'];
1145
1146 $formResult['html'] = '';
1147 $formResult['doSaveFieldName'] = 'doSave';
1148
1149 // @todo: Put all the stuff into FormEngine as final "compiler" class
1150 // @todo: This is done here for now to not rewrite addCssFiles()
1151 // @todo: and printNeededJSFunctions() now
1152 $this->formResultCompiler->mergeResult($formResult);
1153
1154 // Seems the pid is set as hidden field (again) at end?!
1155 if ($command == 'new') {
1156 // @todo: looks ugly
1157 $html .= LF
1158 . '<input type="hidden"'
1159 . ' name="data[' . htmlspecialchars($table) . '][' . htmlspecialchars($formData['databaseRow']['uid']) . '][pid]"'
1160 . ' value="' . (int)$formData['databaseRow']['pid'] . '" />';
1161 $this->newC++;
1162 }
1163
1164 $editForm .= $html;
1165 } catch (AccessDeniedException $e) {
1166 $this->errorC++;
1167 // Try to fetch error message from "recordInternals" be user object
1168 // @todo: This construct should be logged and localized and de-uglified
1169 $message = $beUser->errorMsg;
1170 if (empty($message)) {
1171 // Create message from exception.
1172 $message = $e->getMessage() . ' ' . $e->getCode();
1173 }
1174 $editForm .= htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noEditPermission'))
1175 . '<br /><br />' . htmlspecialchars($message) . '<br /><br />';
1176 }
1177 } // End of for each uid
1178 }
1179 }
1180 }
1181 }
1182 return $editForm;
1183 }
1184
1185 /**
1186 * Create the panel of buttons for submitting the form or otherwise perform operations.
1187 *
1188 * @return array All available buttons as an assoc. array
1189 */
1190 protected function getButtons()
1191 {
1192 $lang = $this->getLanguageService();
1193 // Render SAVE type buttons:
1194 // The action of each button is decided by its name attribute. (See doProcessData())
1195 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1196 if (!$this->errorC && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']) {
1197 $saveSplitButton = $buttonBar->makeSplitButton();
1198 // SAVE button:
1199 $saveButton = $buttonBar->makeInputButton()
1200 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDoc'))
1201 ->setName('_savedok')
1202 ->setValue('1')
1203 ->setForm('EditDocumentController')
1204 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL));
1205 $saveSplitButton->addItem($saveButton, true);
1206
1207 // SAVE / VIEW button:
1208 if ($this->viewId && !$this->noView && $this->getNewIconMode($this->firstEl['table'], 'saveDocView')) {
1209 $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
1210 if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1211 $excludeDokTypes = GeneralUtility::intExplode(
1212 ',',
1213 $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
1214 true
1215 );
1216 } else {
1217 // exclude sysfolders, spacers and recycler by default
1218 $excludeDokTypes = [
1219 PageRepository::DOKTYPE_RECYCLER,
1220 PageRepository::DOKTYPE_SYSFOLDER,
1221 PageRepository::DOKTYPE_SPACER
1222 ];
1223 }
1224 if (!in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, true)
1225 || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'] . '.']['previewPageId'])
1226 ) {
1227 $saveAndOpenButton = $buttonBar->makeInputButton()
1228 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDocShow'))
1229 ->setName('_savedokview')
1230 ->setValue('1')
1231 ->setForm('EditDocumentController')
1232 ->setOnClick("window.open('', 'newTYPO3frontendWindow');")
1233 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1234 'actions-document-save-view',
1235 Icon::SIZE_SMALL
1236 ));
1237 $saveSplitButton->addItem($saveAndOpenButton);
1238 }
1239 }
1240 // SAVE / NEW button:
1241 if (count($this->elementsData) === 1 && $this->getNewIconMode($this->firstEl['table'])) {
1242 $saveAndNewButton = $buttonBar->makeInputButton()
1243 ->setName('_savedoknew')
1244 ->setClasses('t3js-editform-submitButton')
1245 ->setValue('1')
1246 ->setForm('EditDocumentController')
1247 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveNewDoc'))
1248 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1249 'actions-document-save-new',
1250 Icon::SIZE_SMALL
1251 ));
1252 $saveSplitButton->addItem($saveAndNewButton);
1253 }
1254 // SAVE / CLOSE
1255 $saveAndCloseButton = $buttonBar->makeInputButton()
1256 ->setName('_saveandclosedok')
1257 ->setClasses('t3js-editform-submitButton')
1258 ->setValue('1')
1259 ->setForm('EditDocumentController')
1260 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveCloseDoc'))
1261 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1262 'actions-document-save-close',
1263 Icon::SIZE_SMALL
1264 ));
1265 $saveSplitButton->addItem($saveAndCloseButton);
1266 // FINISH TRANSLATION / SAVE / CLOSE
1267 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation']) {
1268 $saveTranslationButton = $buttonBar->makeInputButton()
1269 ->setName('_translation_savedok')
1270 ->setValue('1')
1271 ->setForm('EditDocumentController')
1272 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDoc'))
1273 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1274 'actions-document-save-cleartranslationcache',
1275 Icon::SIZE_SMALL
1276 ));
1277 $saveSplitButton->addItem($saveTranslationButton);
1278 $saveAndClearTranslationButton = $buttonBar->makeInputButton()
1279 ->setName('_translation_savedokclear')
1280 ->setValue('1')
1281 ->setForm('EditDocumentController')
1282 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDocClear'))
1283 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1284 'actions-document-save-cleartranslationcache',
1285 Icon::SIZE_SMALL
1286 ));
1287 $saveSplitButton->addItem($saveAndClearTranslationButton);
1288 }
1289 $buttonBar->addButton($saveSplitButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1290 }
1291 // CLOSE button:
1292 $closeButton = $buttonBar->makeLinkButton()
1293 ->setHref('#')
1294 ->setClasses('t3js-editform-close')
1295 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.closeDoc'))
1296 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1297 'actions-document-close',
1298 Icon::SIZE_SMALL
1299 ));
1300 $buttonBar->addButton($closeButton);
1301 // DELETE + UNDO buttons:
1302 if (!$this->errorC
1303 && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1304 && count($this->elementsData) === 1
1305 ) {
1306 if ($this->firstEl['cmd'] !== 'new' && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])) {
1307 // Delete:
1308 if ($this->firstEl['deleteAccess']
1309 && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1310 && !$this->getNewIconMode($this->firstEl['table'], 'disableDelete')
1311 ) {
1312 $returnUrl = $this->retUrl;
1313 if ($this->firstEl['table'] === 'pages') {
1314 parse_str((string)parse_url($returnUrl, PHP_URL_QUERY), $queryParams);
1315 if (isset($queryParams['M'])
1316 && isset($queryParams['id'])
1317 && (string)$this->firstEl['uid'] === (string)$queryParams['id']
1318 ) {
1319 // TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
1320 // tree from the outside to be able to mark the pid as active
1321 $returnUrl = BackendUtility::getModuleUrl($queryParams['M'], ['id' => 0]);
1322 }
1323 }
1324 $deleteButton = $buttonBar->makeLinkButton()
1325 ->setHref('#')
1326 ->setClasses('t3js-editform-delete-record')
1327 ->setTitle($lang->getLL('deleteItem'))
1328 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1329 'actions-edit-delete',
1330 Icon::SIZE_SMALL
1331 ))
1332 ->setDataAttributes([
1333 'return-url' => $returnUrl,
1334 'uid' => $this->firstEl['uid'],
1335 'table' => $this->firstEl['table']
1336 ]);
1337 $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1338 }
1339 // Undo:
1340 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1341 ->getQueryBuilderForTable('sys_history');
1342
1343 $undoButtonR = $queryBuilder->select('tstamp')
1344 ->from('sys_history')
1345 ->where(
1346 $queryBuilder->expr()->eq(
1347 'tablename',
1348 $queryBuilder->createNamedParameter($this->firstEl['table'])
1349 ),
1350 $queryBuilder->expr()->eq('recuid', (int)$this->firstEl['uid'])
1351 )
1352 ->orderBy('tstamp', 'DESC')
1353 ->setMaxResults(1)
1354 ->execute()
1355 ->fetch();
1356
1357 if ($undoButtonR !== false) {
1358 $aOnClick = 'window.location.href=' .
1359 GeneralUtility::quoteJSvalue(
1360 BackendUtility::getModuleUrl(
1361 'record_history',
1362 [
1363 'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1364 'revert' => 'ALL_FIELDS',
1365 'returnUrl' => $this->R_URI,
1366 ]
1367 )
1368 ) . '; return false;';
1369
1370 $undoButton = $buttonBar->makeLinkButton()
1371 ->setHref('#')
1372 ->setOnClick($aOnClick)
1373 ->setTitle(
1374 sprintf(
1375 $lang->getLL('undoLastChange'),
1376 BackendUtility::calcAge(
1377 ($GLOBALS['EXEC_TIME'] - $undoButtonR['tstamp']),
1378 $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears')
1379 )
1380 )
1381 )
1382 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1383 'actions-document-history-open',
1384 Icon::SIZE_SMALL
1385 ));
1386 $buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1387 }
1388 if ($this->getNewIconMode($this->firstEl['table'], 'showHistory')) {
1389 $aOnClick = 'window.location.href=' .
1390 GeneralUtility::quoteJSvalue(
1391 BackendUtility::getModuleUrl(
1392 'record_history',
1393 [
1394 'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1395 'returnUrl' => $this->R_URI,
1396 ]
1397 )
1398 ) . '; return false;';
1399
1400 $historyButton = $buttonBar->makeLinkButton()
1401 ->setHref('#')
1402 ->setOnClick($aOnClick)
1403 ->setTitle('Open history of this record')
1404 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1405 'actions-document-history-open',
1406 Icon::SIZE_SMALL
1407 ));
1408 $buttonBar->addButton($historyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1409 }
1410 // If only SOME fields are shown in the form, this will link the user to the FULL form:
1411 if ($this->columnsOnly) {
1412 $columnsOnlyButton = $buttonBar->makeLinkButton()
1413 ->setHref($this->R_URI . '&columnsOnly=')
1414 ->setTitle($lang->getLL('editWholeRecord'))
1415 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1416 'actions-open',
1417 Icon::SIZE_SMALL
1418 ));
1419 $buttonBar->addButton($columnsOnlyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1420 }
1421 }
1422 }
1423 $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('TCEforms');
1424 $buttonBar->addButton($cshButton);
1425 $this->shortCutLink();
1426 $this->openInNewWindowLink();
1427 }
1428
1429 /**
1430 * Put together the various elements (buttons, selectors, form) into a table
1431 *
1432 * @param string $editForm HTML form.
1433 * @return string Composite HTML
1434 */
1435 public function compileForm($editForm)
1436 {
1437 $formContent = '
1438 <!-- EDITING FORM -->
1439 <form
1440 action="' . htmlspecialchars($this->R_URI) . '"
1441 method="post"
1442 enctype="multipart/form-data"
1443 name="editform"
1444 id="EditDocumentController"
1445 onsubmit="TBE_EDITOR.checkAndDoSubmit(1); return false;">
1446 ' . $editForm . '
1447
1448 <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
1449 <input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl) . '" />';
1450 if ($this->returnNewPageId) {
1451 $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1452 }
1453 $formContent .= '<input type="hidden" name="popViewId" value="' . htmlspecialchars($this->viewId) . '" />';
1454 if ($this->viewId_addParams) {
1455 $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams) . '" />';
1456 }
1457 $formContent .= '
1458 <input type="hidden" name="closeDoc" value="0" />
1459 <input type="hidden" name="doSave" value="0" />
1460 <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1461 <input type="hidden" name="_scrollPosition" value="" />';
1462 return $formContent;
1463 }
1464
1465 /**
1466 * Create shortcut icon
1467 */
1468 public function shortCutLink()
1469 {
1470 if ($this->returnUrl !== ExtensionManagementUtility::siteRelPath('backend') . 'Resources/Private/Templates/Close.html') {
1471 $shortCutButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton();
1472 $shortCutButton->setModuleName($this->MCONF['name'])
1473 ->setGetVariables([
1474 'returnUrl',
1475 'edit',
1476 'defVals',
1477 'overrideVals',
1478 'columnsOnly',
1479 'returnNewPageId',
1480 'noView']);
1481 $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton($shortCutButton);
1482 }
1483 }
1484
1485 /**
1486 * Creates open-in-window link
1487 */
1488 public function openInNewWindowLink()
1489 {
1490 $closeUrl = ExtensionManagementUtility::siteRelPath('backend') . 'Resources/Private/Templates/Close.html';
1491 if ($this->returnUrl !== $closeUrl) {
1492 $aOnClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript(
1493 ['returnUrl' => PathUtility::getAbsoluteWebPath($closeUrl)]
1494 ))
1495 . ','
1496 . GeneralUtility::quoteJSvalue(md5($this->R_URI))
1497 . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
1498 $openInNewWindowButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()
1499 ->makeLinkButton()
1500 ->setHref('#')
1501 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow'))
1502 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-window-open', Icon::SIZE_SMALL))
1503 ->setOnClick($aOnClick);
1504 $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton(
1505 $openInNewWindowButton,
1506 ButtonBar::BUTTON_POSITION_RIGHT
1507 );
1508 }
1509 }
1510
1511 /***************************
1512 *
1513 * Localization stuff
1514 *
1515 ***************************/
1516 /**
1517 * Make selector box for creating new translation for a record or switching to edit the record in an existing
1518 * language.
1519 * Displays only languages which are available for the current page.
1520 *
1521 * @param string $table Table name
1522 * @param int $uid Uid for which to create a new language
1523 * @param int $pid Pid of the record
1524 */
1525 public function languageSwitch($table, $uid, $pid = null)
1526 {
1527 $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1528 $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1529 // Table editable and activated for languages?
1530 if ($this->getBackendUser()->check('tables_modify', $table)
1531 && $languageField
1532 && $transOrigPointerField && !$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']
1533 ) {
1534 if (is_null($pid)) {
1535 $row = BackendUtility::getRecord($table, $uid, 'pid');
1536 $pid = $row['pid'];
1537 }
1538 // Get all available languages for the page
1539 $langRows = $this->getLanguages($pid);
1540 // Page available in other languages than default language?
1541 if (is_array($langRows) && count($langRows) > 1) {
1542 $rowsByLang = [];
1543 $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1544 // Get record in current language
1545 $rowCurrent = BackendUtility::getLiveVersionOfRecord($table, $uid, $fetchFields);
1546 if (!is_array($rowCurrent)) {
1547 $rowCurrent = BackendUtility::getRecord($table, $uid, $fetchFields);
1548 }
1549 $currentLanguage = (int)$rowCurrent[$languageField];
1550 // Disabled for records with [all] language!
1551 if ($currentLanguage > -1) {
1552 // Get record in default language if needed
1553 if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1554 $rowsByLang[0] = BackendUtility::getLiveVersionOfRecord(
1555 $table,
1556 $rowCurrent[$transOrigPointerField],
1557 $fetchFields
1558 );
1559 if (!is_array($rowsByLang[0])) {
1560 $rowsByLang[0] = BackendUtility::getRecord(
1561 $table,
1562 $rowCurrent[$transOrigPointerField],
1563 $fetchFields
1564 );
1565 }
1566 } else {
1567 $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1568 }
1569 if ($rowCurrent[$transOrigPointerField] || $currentLanguage === 0) {
1570 // Get record in other languages to see what's already available
1571
1572 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1573 ->getQueryBuilderForTable($table);
1574
1575 $queryBuilder->getRestrictions()
1576 ->removeAll()
1577 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1578 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1579
1580 $result = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fetchFields, true))
1581 ->from($table)
1582 ->where(
1583 $queryBuilder->expr()->eq('pid', (int)$pid),
1584 $queryBuilder->expr()->gt($languageField, 0),
1585 $queryBuilder->expr()->eq($transOrigPointerField, (int)$rowsByLang[0]['uid'])
1586 )
1587 ->execute();
1588
1589 while ($row = $result->fetch()) {
1590 $rowsByLang[$row[$languageField]] = $row;
1591 }
1592 }
1593 $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1594 $languageMenu->setIdentifier('_langSelector');
1595 $languageMenu->setLabel(htmlspecialchars($this->getLanguageService()->sL(
1596 'LLL:EXT:lang/locallang_general.xlf:LGL.language'
1597 )));
1598 foreach ($langRows as $lang) {
1599 if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
1600 $newTranslation = isset($rowsByLang[$lang['uid']]) ? '' : ' [' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.new')) . ']';
1601 // Create url for creating a localized record
1602 if ($newTranslation) {
1603 $redirectUrl = BackendUtility::getModuleUrl('record_edit', [
1604 'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
1605 'returnUrl' => $this->retUrl
1606 ]);
1607 $href = BackendUtility::getLinkToDataHandlerAction(
1608 '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'],
1609 $redirectUrl
1610 );
1611 } else {
1612 $href = BackendUtility::getModuleUrl('record_edit', [
1613 'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
1614 'returnUrl' => $this->retUrl
1615 ]);
1616 }
1617 $menuItem = $languageMenu->makeMenuItem()
1618 ->setTitle($lang['title'] . $newTranslation)
1619 ->setHref($href);
1620 if ((int)$lang['uid'] === $currentLanguage) {
1621 $menuItem->setActive(true);
1622 }
1623 $languageMenu->addMenuItem($menuItem);
1624 }
1625 }
1626 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
1627 }
1628 }
1629 }
1630 }
1631
1632 /**
1633 * Redirects to FormEngine with new parameters to edit a just created localized record
1634 *
1635 * @param string $justLocalized String passed by GET &justLocalized=
1636 * @return void
1637 */
1638 public function localizationRedirect($justLocalized)
1639 {
1640 list($table, $origUid, $language) = explode(':', $justLocalized);
1641 if ($GLOBALS['TCA'][$table]
1642 && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1643 && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1644 ) {
1645 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1646 ->getQueryBuilderForTable($table);
1647 $queryBuilder->getRestrictions()
1648 ->removeAll()
1649 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1650 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1651
1652 $localizedRecord = $queryBuilder->select('uid')
1653 ->from($table)
1654 ->where(
1655 $queryBuilder->expr()->eq($GLOBALS['TCA'][$table]['ctrl']['languageField'], (int)$language),
1656 $queryBuilder->expr()->eq($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], (int)$origUid)
1657 )
1658 ->execute()
1659 ->fetch();
1660
1661 if (is_array($localizedRecord)) {
1662 // Create parameters and finally run the classic page module for creating a new page translation
1663 $location = BackendUtility::getModuleUrl('record_edit', [
1664 'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
1665 'returnUrl' => GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))
1666 ]);
1667 HttpUtility::redirect($location);
1668 }
1669 }
1670 }
1671
1672 /**
1673 * Returns sys_language records available for record translations on given page.
1674 *
1675 * @param int $id Page id: If zero, the query will select all sys_language records from root level which are NOT
1676 * hidden. If set to another value, the query will select all sys_language records that has a
1677 * pages_language_overlay record on that page (and is not hidden, unless you are admin user)
1678 * @return array Language records including faked record for default language
1679 */
1680 public function getLanguages($id)
1681 {
1682 $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
1683 // Fallback non sprite-configuration
1684 if (preg_match('/\\.gif$/', $modSharedTSconfig['properties']['defaultLanguageFlag'])) {
1685 $modSharedTSconfig['properties']['defaultLanguageFlag'] = str_replace(
1686 '.gif',
1687 '',
1688 $modSharedTSconfig['properties']['defaultLanguageFlag']
1689 );
1690 }
1691 $languages = [
1692 0 => [
1693 'uid' => 0,
1694 'pid' => 0,
1695 'hidden' => 0,
1696 'title' => $modSharedTSconfig['properties']['defaultLanguageLabel'] !== ''
1697 ? $modSharedTSconfig['properties']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage') . ')'
1698 : $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage'),
1699 'flag' => $modSharedTSconfig['properties']['defaultLanguageFlag']
1700 ]
1701 ];
1702
1703 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1704 ->getQueryBuilderForTable('sys_language');
1705
1706 $queryBuilder->select('s.uid', 's.pid', 's.hidden', 's.title', 's.flag')
1707 ->from('sys_language', 's')
1708 ->groupBy('s.uid', 's.pid', 's.hidden', 's.title', 's.flag')
1709 ->orderBy('s.sorting');
1710
1711 if ($id) {
1712 $queryBuilder->getRestrictions()
1713 ->removeAll()
1714 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1715 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1716
1717 if (!$this->getBackendUser()->isAdmin()) {
1718 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
1719 }
1720
1721 // Add join with pages_languages_overlay table to only show active languages
1722 $queryBuilder->from('pages_language_overlay', 'o')
1723 ->where(
1724 $queryBuilder->expr()->eq(
1725 'o.sys_language_uid',
1726 $queryBuilder->quoteIdentifier('s.uid')
1727 ),
1728 $queryBuilder->expr()->eq('o.pid', (int)$id)
1729 );
1730 }
1731
1732 $result = $queryBuilder->execute();
1733 while ($row = $result->fetch()) {
1734 $languages[$row['uid']] = $row;
1735 }
1736
1737 return $languages;
1738 }
1739
1740 /***************************
1741 *
1742 * Other functions
1743 *
1744 ***************************/
1745 /**
1746 * Fix $this->editconf if versioning applies to any of the records
1747 *
1748 * @param array|bool $mapArray Mapping between old and new ids if auto-versioning has been performed.
1749 * @return void
1750 */
1751 public function fixWSversioningInEditConf($mapArray = false)
1752 {
1753 // Traverse the editConf array
1754 if (is_array($this->editconf)) {
1755 // Tables:
1756 foreach ($this->editconf as $table => $conf) {
1757 if (is_array($conf) && $GLOBALS['TCA'][$table]) {
1758 // Traverse the keys/comments of each table (keys can be a commalist of uids)
1759 $newConf = [];
1760 foreach ($conf as $cKey => $cmd) {
1761 if ($cmd == 'edit') {
1762 // Traverse the ids:
1763 $ids = GeneralUtility::trimExplode(',', $cKey, true);
1764 foreach ($ids as $idKey => $theUid) {
1765 if (is_array($mapArray)) {
1766 if ($mapArray[$table][$theUid]) {
1767 $ids[$idKey] = $mapArray[$table][$theUid];
1768 }
1769 } else {
1770 // Default, look for versions in workspace for record:
1771 $calcPRec = $this->getRecordForEdit($table, $theUid);
1772 if (is_array($calcPRec)) {
1773 // Setting UID again if it had changed, eg. due to workspace versioning.
1774 $ids[$idKey] = $calcPRec['uid'];
1775 }
1776 }
1777 }
1778 // Add the possibly manipulated IDs to the new-build newConf array:
1779 $newConf[implode(',', $ids)] = $cmd;
1780 } else {
1781 $newConf[$cKey] = $cmd;
1782 }
1783 }
1784 // Store the new conf array:
1785 $this->editconf[$table] = $newConf;
1786 }
1787 }
1788 }
1789 }
1790
1791 /**
1792 * Get record for editing.
1793 *
1794 * @param string $table Table name
1795 * @param int $theUid Record UID
1796 * @return array Returns record to edit, FALSE if none
1797 */
1798 public function getRecordForEdit($table, $theUid)
1799 {
1800 // Fetch requested record:
1801 $reqRecord = BackendUtility::getRecord($table, $theUid, 'uid,pid');
1802 if (is_array($reqRecord)) {
1803 // If workspace is OFFLINE:
1804 if ($this->getBackendUser()->workspace != 0) {
1805 // Check for versioning support of the table:
1806 if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1807 // If the record is already a version of "something" pass it by.
1808 if ($reqRecord['pid'] == -1) {
1809 // (If it turns out not to be a version of the current workspace there will be trouble, but
1810 // that is handled inside TCEmain then and in the interface it would clearly be an error of
1811 // links if the user accesses such a scenario)
1812 return $reqRecord;
1813 } else {
1814 // The input record was online and an offline version must be found or made:
1815 // Look for version of this workspace:
1816 $versionRec = BackendUtility::getWorkspaceVersionOfRecord(
1817 $this->getBackendUser()->workspace,
1818 $table,
1819 $reqRecord['uid'],
1820 'uid,pid,t3ver_oid'
1821 );
1822 return is_array($versionRec) ? $versionRec : $reqRecord;
1823 }
1824 } else {
1825 // This means that editing cannot occur on this record because it was not supporting versioning
1826 // which is required inside an offline workspace.
1827 return false;
1828 }
1829 } else {
1830 // In ONLINE workspace, just return the originally requested record:
1831 return $reqRecord;
1832 }
1833 } else {
1834 // Return FALSE because the table/uid was not found anyway.
1835 return false;
1836 }
1837 }
1838
1839 /**
1840 * Populates the variables $this->storeArray, $this->storeUrl, $this->storeUrlMd5
1841 *
1842 * @return void
1843 * @see makeDocSel()
1844 */
1845 public function compileStoreDat()
1846 {
1847 $this->storeArray = GeneralUtility::compileSelectedGetVarsFromArray(
1848 'edit,defVals,overrideVals,columnsOnly,noView,workspace',
1849 $this->R_URL_getvars
1850 );
1851 $this->storeUrl = GeneralUtility::implodeArrayForUrl('', $this->storeArray);
1852 $this->storeUrlMd5 = md5($this->storeUrl);
1853 }
1854
1855 /**
1856 * Function used to look for configuration of buttons in the form: Fx. disabling buttons or showing them at various
1857 * positions.
1858 *
1859 * @param string $table The table for which the configuration may be specific
1860 * @param string $key The option for look for. Default is checking if the saveDocNew button should be displayed.
1861 * @return string Return value fetched from USER TSconfig
1862 */
1863 public function getNewIconMode($table, $key = 'saveDocNew')
1864 {
1865 $TSconfig = $this->getBackendUser()->getTSConfig('options.' . $key);
1866 $output = trim(isset($TSconfig['properties'][$table]) ? $TSconfig['properties'][$table] : $TSconfig['value']);
1867 return $output;
1868 }
1869
1870 /**
1871 * Handling the closing of a document
1872 *
1873 * @param int $code Close code: 0/1 will redirect to $this->retUrl, 3 will clear the docHandler (thus closing all
1874 * documents) and other values will call setDocument with ->retUrl
1875 * @return void
1876 */
1877 public function closeDocument($code = 0)
1878 {
1879 // If current document is found in docHandler,
1880 // then unset it, possibly unset it ALL and finally, write it to the session data
1881 if (isset($this->docHandler[$this->storeUrlMd5])) {
1882 // add the closing document to the recent documents
1883 $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
1884 if (!is_array($recentDocs)) {
1885 $recentDocs = [];
1886 }
1887 $closedDoc = $this->docHandler[$this->storeUrlMd5];
1888 $recentDocs = array_merge([$this->storeUrlMd5 => $closedDoc], $recentDocs);
1889 if (count($recentDocs) > 8) {
1890 $recentDocs = array_slice($recentDocs, 0, 8);
1891 }
1892 // remove it from the list of the open documents
1893 unset($this->docHandler[$this->storeUrlMd5]);
1894 if ($code == '3') {
1895 $recentDocs = array_merge($this->docHandler, $recentDocs);
1896 $this->docHandler = [];
1897 }
1898 $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
1899 $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->docDat[1]]);
1900 BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
1901 }
1902 // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: (used by
1903 // other scripts, like wizard_add, to know which records was created or so...)
1904 if ($this->returnEditConf && $this->retUrl != BackendUtility::getModuleUrl('dummy')) {
1905 $this->retUrl .= '&returnEditConf=' . rawurlencode(json_encode($this->editconf));
1906 }
1907 // If code is NOT set OR set to 1, then make a header location redirect to $this->retUrl
1908 if (!$code || $code == 1) {
1909 HttpUtility::redirect($this->retUrl);
1910 } else {
1911 $this->setDocument('', $this->retUrl);
1912 }
1913 }
1914
1915 /**
1916 * Redirects to the document pointed to by $currentDocFromHandlerMD5 OR $retUrl (depending on some internal
1917 * calculations).
1918 * Most likely you will get a header-location redirect from this function.
1919 *
1920 * @param string $currentDocFromHandlerMD5 Pointer to the document in the docHandler array
1921 * @param string $retUrl Alternative/Default retUrl
1922 * @return void
1923 */
1924 public function setDocument($currentDocFromHandlerMD5 = '', $retUrl = '')
1925 {
1926 if ($retUrl === '') {
1927 return;
1928 }
1929 if (!$this->modTSconfig['properties']['disableDocSelector']
1930 && is_array($this->docHandler)
1931 && !empty($this->docHandler)
1932 ) {
1933 if (isset($this->docHandler[$currentDocFromHandlerMD5])) {
1934 $setupArr = $this->docHandler[$currentDocFromHandlerMD5];
1935 } else {
1936 $setupArr = reset($this->docHandler);
1937 }
1938 if ($setupArr[2]) {
1939 $sParts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
1940 $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
1941 }
1942 }
1943 HttpUtility::redirect($retUrl);
1944 }
1945
1946 /**
1947 * Injects the request object for the current request or subrequest
1948 *
1949 * @param ServerRequestInterface $request the current request
1950 * @param ResponseInterface $response
1951 * @return ResponseInterface the response with the content
1952 */
1953 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
1954 {
1955 BackendUtility::lockRecords();
1956
1957 // Preprocessing, storing data if submitted to
1958 $this->preInit();
1959
1960 // Checks, if a save button has been clicked (or the doSave variable is sent)
1961 if ($this->doProcessData()) {
1962 $this->processData();
1963 }
1964
1965 $this->init();
1966 $this->main();
1967
1968 $response->getBody()->write($this->moduleTemplate->renderContent());
1969 return $response;
1970 }
1971
1972 /**
1973 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1974 */
1975 protected function getBackendUser()
1976 {
1977 return $GLOBALS['BE_USER'];
1978 }
1979
1980 /**
1981 * Returns LanguageService
1982 *
1983 * @return \TYPO3\CMS\Lang\LanguageService
1984 */
1985 protected function getLanguageService()
1986 {
1987 return $GLOBALS['LANG'];
1988 }
1989 }