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