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