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