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