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