[BUGFIX] Set correct record information for SaveAndNew
[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', array($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 array($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 = array();
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 = array();
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 = array(
756 'PopupWindow' => array(
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 = array(
768 'csh_tooltip_loading' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:csh_tooltip_loading')
769 );
770 $generatedLabels = array();
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 : array();
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 if ((int)$currentPage['doktype'] === PageRepository::DOKTYPE_DEFAULT
869 || (int)$currentPage['doktype'] === PageRepository::DOKTYPE_LINK
870 || (int)$currentPage['doktype'] === PageRepository::DOKTYPE_SHORTCUT
871 || (int)$currentPage['doktype'] === PageRepository::DOKTYPE_MOUNTPOINT
872 ) {
873 // try the current page
874 $previewPageId = $currentPageId;
875 } else {
876 // or search for the root page
877 foreach ($rootLine as $page) {
878 if ($page['is_siteroot']) {
879 $rootPageData = $page;
880 break;
881 }
882 }
883 $previewPageId = isset($rootPageData)
884 ? (int)$rootPageData['uid']
885 : $currentPageId;
886 }
887 }
888
889 $linkParameters = [
890 'no_cache' => 1,
891 ];
892
893 // language handling
894 $languageField = isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
895 ? $GLOBALS['TCA'][$table]['ctrl']['languageField']
896 : '';
897 if ($languageField && !empty($recordArray[$languageField])) {
898 $l18nPointer = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
899 ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
900 : '';
901 if ($l18nPointer && !empty($recordArray[$l18nPointer])
902 && isset($previewConfiguration['useDefaultLanguageRecord'])
903 && !$previewConfiguration['useDefaultLanguageRecord']
904 ) {
905 // use parent record
906 $recordId = $recordArray[$l18nPointer];
907 }
908 $linkParameters['L'] = $recordArray[$languageField];
909 }
910
911 // map record data to GET parameters
912 if (isset($previewConfiguration['fieldToParameterMap.'])) {
913 foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
914 $value = $recordArray[$field];
915 if ($field === 'uid') {
916 $value = $recordId;
917 }
918 $linkParameters[$parameterName] = $value;
919 }
920 }
921
922 // add/override parameters by configuration
923 if (isset($previewConfiguration['additionalGetParameters.'])) {
924 $additionalGetParameters = [];
925 $this->parseAdditionalGetParameters(
926 $additionalGetParameters,
927 $previewConfiguration['additionalGetParameters.']
928 );
929 $linkParameters = array_replace($linkParameters, $additionalGetParameters);
930 }
931
932 $this->popViewId = $previewPageId;
933 $this->popViewId_addParams = GeneralUtility::implodeArrayForUrl('', $linkParameters, '', false, true);
934
935 $previewPageRootline = BackendUtility::BEgetRootLine($this->popViewId);
936 return '
937 if (window.opener) {
938 '
939 . BackendUtility::viewOnClick(
940 $this->popViewId,
941 '',
942 $previewPageRootline,
943 '',
944 $this->viewUrl,
945 $this->popViewId_addParams,
946 false
947 )
948 . '
949 } else {
950 '
951 . BackendUtility::viewOnClick(
952 $this->popViewId,
953 '',
954 $previewPageRootline,
955 '',
956 $this->viewUrl,
957 $this->popViewId_addParams
958 )
959 . '
960 }';
961 }
962
963 /**
964 * Migrates a set of (possibly nested) GET parameters in TypoScript syntax to a plain array
965 *
966 * This basically removes the trailing dots of sub-array keys in TypoScript.
967 * The result can be used to create a query string with GeneralUtility::implodeArrayForUrl().
968 *
969 * @param array $parameters Should be an empty array by default
970 * @param array $typoScript The TypoScript configuration
971 */
972 protected function parseAdditionalGetParameters(array &$parameters, array $typoScript)
973 {
974 foreach ($typoScript as $key => $value) {
975 if (is_array($value)) {
976 $key = rtrim($key, '.');
977 $parameters[$key] = [];
978 $this->parseAdditionalGetParameters($parameters[$key], $value);
979 } else {
980 $parameters[$key] = $value;
981 }
982 }
983 }
984
985 /**
986 * Main module operation
987 *
988 * @return void
989 */
990 public function main()
991 {
992 $body = '';
993 // Begin edit:
994 if (is_array($this->editconf)) {
995 /** @var FormResultCompiler formResultCompiler */
996 $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
997
998 if ($this->editRegularContentFromId) {
999 $this->editRegularContentFromId();
1000 }
1001 // Creating the editing form, wrap it with buttons, document selector etc.
1002 $editForm = $this->makeEditForm();
1003 if ($editForm) {
1004 $this->firstEl = reset($this->elementsData);
1005 // Checking if the currently open document is stored in the list of "open documents" - if not, add it:
1006 if (($this->docDat[1] !== $this->storeUrlMd5
1007 || !isset($this->docHandler[$this->storeUrlMd5]))
1008 && !$this->dontStoreDocumentRef
1009 ) {
1010 $this->docHandler[$this->storeUrlMd5] = array(
1011 $this->storeTitle,
1012 $this->storeArray,
1013 $this->storeUrl,
1014 $this->firstEl
1015 );
1016 $this->getBackendUser()->pushModuleData('FormEngine', array($this->docHandler, $this->storeUrlMd5));
1017 BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
1018 }
1019 // Module configuration
1020 $this->modTSconfig = $this->viewId ? BackendUtility::getModTSconfig(
1021 $this->viewId,
1022 'mod.xMOD_alt_doc'
1023 ) : array();
1024 $body = $this->formResultCompiler->JStop();
1025 $body .= $this->compileForm($editForm);
1026 $body .= $this->formResultCompiler->printNeededJSFunctions();
1027 $body .= '</form>';
1028 }
1029 }
1030 // Access check...
1031 // The page will show only if there is a valid page and if this page may be viewed by the user
1032 $this->pageinfo = BackendUtility::readPageAccess($this->viewId, $this->perms_clause);
1033 if ($this->pageinfo) {
1034 $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
1035 }
1036 // Setting up the buttons and markers for docheader
1037 $this->getButtons();
1038 $this->languageSwitch($this->firstEl['table'], $this->firstEl['uid'], $this->firstEl['pid']);
1039 $this->moduleTemplate->setContent($body);
1040 }
1041
1042 /**
1043 * Outputting the accumulated content to screen
1044 *
1045 * @return void
1046 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8
1047 */
1048 public function printContent()
1049 {
1050 GeneralUtility::logDeprecatedFunction();
1051 echo $this->content;
1052 }
1053
1054 /***************************
1055 *
1056 * Sub-content functions, rendering specific parts of the module content.
1057 *
1058 ***************************/
1059 /**
1060 * Creates the editing form with FormEnigne, based on the input from GPvars.
1061 *
1062 * @return string HTML form elements wrapped in tables
1063 */
1064 public function makeEditForm()
1065 {
1066 // Initialize variables:
1067 $this->elementsData = array();
1068 $this->errorC = 0;
1069 $this->newC = 0;
1070 $editForm = '';
1071 $trData = null;
1072 $beUser = $this->getBackendUser();
1073 // Traverse the GPvar edit array
1074 // Tables:
1075 foreach ($this->editconf as $table => $conf) {
1076 if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
1077 // Traverse the keys/comments of each table (keys can be a commalist of uids)
1078 foreach ($conf as $cKey => $command) {
1079 if ($command == 'edit' || $command == 'new') {
1080 // Get the ids:
1081 $ids = GeneralUtility::trimExplode(',', $cKey, true);
1082 // Traverse the ids:
1083 foreach ($ids as $theUid) {
1084 // Don't save this document title in the document selector if the document is new.
1085 if ($command === 'new') {
1086 $this->dontStoreDocumentRef = 1;
1087 }
1088
1089 /** @var TcaDatabaseRecord $formDataGroup */
1090 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
1091 /** @var FormDataCompiler $formDataCompiler */
1092 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
1093 /** @var NodeFactory $nodeFactory */
1094 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
1095
1096 try {
1097 // Reset viewId - it should hold data of last entry only
1098 $this->viewId = 0;
1099 $this->viewId_addParams = '';
1100
1101 $formDataCompilerInput = [
1102 'tableName' => $table,
1103 'vanillaUid' => (int)$theUid,
1104 'command' => $command,
1105 'returnUrl' => $this->R_URI,
1106 ];
1107 if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
1108 $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
1109 }
1110
1111 $formData = $formDataCompiler->compile($formDataCompilerInput);
1112
1113 // Set this->viewId if possible
1114 if ($command === 'new'
1115 && $table !== 'pages'
1116 && !empty($formData['parentPageRow']['uid'])
1117 ) {
1118 $this->viewId = $formData['parentPageRow']['uid'];
1119 } else {
1120 if ($table == 'pages') {
1121 $this->viewId = $formData['databaseRow']['uid'];
1122 } elseif (!empty($formData['parentPageRow']['uid'])) {
1123 $this->viewId = $formData['parentPageRow']['uid'];
1124 // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
1125 if (!empty($formData['processedTca']['ctrl']['languageField'])
1126 && is_array($formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']])
1127 && $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0] > 0
1128 ) {
1129 $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0];
1130 }
1131 }
1132 }
1133
1134 // Determine if delete button can be shown
1135 $deleteAccess = false;
1136 if ($command === 'edit') {
1137 $permission = $formData['userPermissionOnPage'];
1138 if ($formData['tableName'] === 'pages') {
1139 $deleteAccess = $permission & Permission::PAGE_DELETE ? true : false;
1140 } else {
1141 $deleteAccess = $permission & Permission::CONTENT_EDIT ? true : false;
1142 }
1143 }
1144
1145 // Display "is-locked" message:
1146 if ($command === 'edit') {
1147 $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
1148 if ($lockInfo) {
1149 /** @var $flashMessage \TYPO3\CMS\Core\Messaging\FlashMessage */
1150 $flashMessage = GeneralUtility::makeInstance(
1151 FlashMessage::class,
1152 $lockInfo['msg'],
1153 '',
1154 FlashMessage::WARNING
1155 );
1156 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
1157 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1158 /** @var $defaultFlashMessageQueue FlashMessageQueue */
1159 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1160 $defaultFlashMessageQueue->enqueue($flashMessage);
1161 }
1162 }
1163
1164 // Record title
1165 if (!$this->storeTitle) {
1166 $this->storeTitle = $this->recTitle
1167 ? htmlspecialchars($this->recTitle)
1168 : BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($formData['databaseRow']), true);
1169 }
1170
1171 $this->elementsData[] = array(
1172 'table' => $table,
1173 'uid' => $formData['databaseRow']['uid'],
1174 'pid' => $formData['databaseRow']['pid'],
1175 'cmd' => $command,
1176 'deleteAccess' => $deleteAccess
1177 );
1178
1179 if ($command !== 'new') {
1180 BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
1181 }
1182
1183 // Set list if only specific fields should be rendered. This will trigger
1184 // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1185 if ($this->columnsOnly) {
1186 if (is_array($this->columnsOnly)) {
1187 $formData['fieldListToRender'] = $this->columnsOnly[$table];
1188 } else {
1189 $formData['fieldListToRender'] = $this->columnsOnly;
1190 }
1191 }
1192
1193 $formData['renderType'] = 'outerWrapContainer';
1194 $formResult = $nodeFactory->create($formData)->render();
1195
1196 $html = $formResult['html'];
1197
1198 $formResult['html'] = '';
1199 $formResult['doSaveFieldName'] = 'doSave';
1200
1201 // @todo: Put all the stuff into FormEngine as final "compiler" class
1202 // @todo: This is done here for now to not rewrite JStop()
1203 // @todo: and printNeededJSFunctions() now
1204 $this->formResultCompiler->mergeResult($formResult);
1205
1206 // Seems the pid is set as hidden field (again) at end?!
1207 if ($command == 'new') {
1208 // @todo: looks ugly
1209 $html .= LF
1210 . '<input type="hidden"'
1211 . ' name="data[' . htmlspecialchars($table) . '][' . htmlspecialchars($formData['databaseRow']['uid']) . '][pid]"'
1212 . ' value="' . (int)$formData['databaseRow']['pid'] . '" />';
1213 $this->newC++;
1214 }
1215
1216 $editForm .= $html;
1217 } catch (AccessDeniedException $e) {
1218 $this->errorC++;
1219 // Try to fetch error message from "recordInternals" be user object
1220 // @todo: This construct should be logged and localized and de-uglified
1221 $message = $beUser->errorMsg;
1222 if (empty($message)) {
1223 // Create message from exception.
1224 $message = $e->getMessage() . ' ' . $e->getCode();
1225 }
1226 $editForm .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noEditPermission', true)
1227 . '<br /><br />' . htmlspecialchars($message) . '<br /><br />';
1228 }
1229 } // End of for each uid
1230 }
1231 }
1232 }
1233 }
1234 return $editForm;
1235 }
1236
1237 /**
1238 * Create the panel of buttons for submitting the form or otherwise perform operations.
1239 *
1240 * @return array All available buttons as an assoc. array
1241 */
1242 protected function getButtons()
1243 {
1244 $lang = $this->getLanguageService();
1245 // Render SAVE type buttons:
1246 // The action of each button is decided by its name attribute. (See doProcessData())
1247 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1248 if (!$this->errorC && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']) {
1249 $saveSplitButton = $buttonBar->makeSplitButton();
1250 // SAVE button:
1251 $saveButton = $buttonBar->makeInputButton()
1252 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDoc'))
1253 ->setName('_savedok')
1254 ->setValue('1')
1255 ->setForm('EditDocumentController')
1256 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL));
1257 $saveSplitButton->addItem($saveButton, true);
1258
1259 // SAVE / VIEW button:
1260 if ($this->viewId && !$this->noView && $this->getNewIconMode($this->firstEl['table'], 'saveDocView')) {
1261 $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
1262 if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1263 $excludeDokTypes = GeneralUtility::intExplode(
1264 ',',
1265 $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
1266 true
1267 );
1268 } else {
1269 // exclude sysfolders, spacers and recycler by default
1270 $excludeDokTypes = array(
1271 PageRepository::DOKTYPE_RECYCLER,
1272 PageRepository::DOKTYPE_SYSFOLDER,
1273 PageRepository::DOKTYPE_SPACER
1274 );
1275 }
1276 if (!in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, true)
1277 || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'] . '.']['previewPageId'])
1278 ) {
1279 $saveAndOpenButton = $buttonBar->makeInputButton()
1280 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDocShow'))
1281 ->setName('_savedokview')
1282 ->setValue('1')
1283 ->setForm('EditDocumentController')
1284 ->setOnClick("window.open('', 'newTYPO3frontendWindow');")
1285 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1286 'actions-document-save-view',
1287 Icon::SIZE_SMALL
1288 ));
1289 $saveSplitButton->addItem($saveAndOpenButton);
1290 }
1291 }
1292 // SAVE / NEW button:
1293 if (count($this->elementsData) === 1 && $this->getNewIconMode($this->firstEl['table'])) {
1294 $saveAndNewButton = $buttonBar->makeInputButton()
1295 ->setName('_savedoknew')
1296 ->setClasses('t3js-editform-submitButton')
1297 ->setValue('1')
1298 ->setForm('EditDocumentController')
1299 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveNewDoc'))
1300 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1301 'actions-document-save-new',
1302 Icon::SIZE_SMALL
1303 ));
1304 $saveSplitButton->addItem($saveAndNewButton);
1305 }
1306 // SAVE / CLOSE
1307 $saveAndCloseButton = $buttonBar->makeInputButton()
1308 ->setName('_saveandclosedok')
1309 ->setClasses('t3js-editform-submitButton')
1310 ->setValue('1')
1311 ->setForm('EditDocumentController')
1312 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveCloseDoc'))
1313 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1314 'actions-document-save-close',
1315 Icon::SIZE_SMALL
1316 ));
1317 $saveSplitButton->addItem($saveAndCloseButton);
1318 // FINISH TRANSLATION / SAVE / CLOSE
1319 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation']) {
1320 $saveTranslationButton = $buttonBar->makeInputButton()
1321 ->setName('_translation_savedok')
1322 ->setValue('1')
1323 ->setForm('EditDocumentController')
1324 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDoc'))
1325 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1326 'actions-document-save-cleartranslationcache',
1327 Icon::SIZE_SMALL
1328 ));
1329 $saveSplitButton->addItem($saveTranslationButton);
1330 $saveAndClearTranslationButton = $buttonBar->makeInputButton()
1331 ->setName('_translation_savedokclear')
1332 ->setValue('1')
1333 ->setForm('EditDocumentController')
1334 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDocClear'))
1335 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1336 'actions-document-save-cleartranslationcache',
1337 Icon::SIZE_SMALL
1338 ));
1339 $saveSplitButton->addItem($saveAndClearTranslationButton);
1340 }
1341 $buttonBar->addButton($saveSplitButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1342 }
1343 // CLOSE button:
1344 $closeButton = $buttonBar->makeLinkButton()
1345 ->setHref('#')
1346 ->setClasses('t3js-editform-close')
1347 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.closeDoc'))
1348 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1349 'actions-document-close',
1350 Icon::SIZE_SMALL
1351 ));
1352 $buttonBar->addButton($closeButton);
1353 // DELETE + UNDO buttons:
1354 if (!$this->errorC
1355 && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1356 && count($this->elementsData) === 1
1357 ) {
1358 if ($this->firstEl['cmd'] !== 'new' && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])) {
1359 // Delete:
1360 if ($this->firstEl['deleteAccess']
1361 && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1362 && !$this->getNewIconMode($this->firstEl['table'], 'disableDelete')
1363 ) {
1364 $returnUrl = $this->retUrl;
1365 if ($this->firstEl['table'] === 'pages') {
1366 parse_str((string)parse_url($returnUrl, PHP_URL_QUERY), $queryParams);
1367 if (isset($queryParams['M'])
1368 && isset($queryParams['id'])
1369 && (string)$this->firstEl['uid'] === (string)$queryParams['id']
1370 ) {
1371 // TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
1372 // tree from the outside to be able to mark the pid as active
1373 $returnUrl = BackendUtility::getModuleUrl($queryParams['M'], ['id' => 0]);
1374 }
1375 }
1376 $deleteButton = $buttonBar->makeLinkButton()
1377 ->setHref('#')
1378 ->setClasses('t3js-editform-delete-record')
1379 ->setTitle($lang->getLL('deleteItem'))
1380 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1381 'actions-edit-delete',
1382 Icon::SIZE_SMALL
1383 ))
1384 ->setDataAttributes([
1385 'return-url' => $returnUrl,
1386 'uid' => $this->firstEl['uid'],
1387 'table' => $this->firstEl['table']
1388 ]);
1389 $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1390 }
1391 // Undo:
1392 $undoRes = $this->getDatabaseConnection()->exec_SELECTquery(
1393 'tstamp',
1394 'sys_history',
1395 'tablename='
1396 . $this->getDatabaseConnection()->fullQuoteStr($this->firstEl['table'], 'sys_history')
1397 . ' AND recuid='
1398 . (int)$this->firstEl['uid'],
1399 '',
1400 'tstamp DESC',
1401 '1'
1402 );
1403 if ($undoButtonR = $this->getDatabaseConnection()->sql_fetch_assoc($undoRes)) {
1404 $aOnClick = 'window.location.href=' .
1405 GeneralUtility::quoteJSvalue(
1406 BackendUtility::getModuleUrl(
1407 'record_history',
1408 array(
1409 'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1410 'revert' => 'ALL_FIELDS',
1411 'sumUp' => -1,
1412 'returnUrl' => $this->R_URI,
1413 )
1414 )
1415 ) . '; return false;';
1416
1417 $undoButton = $buttonBar->makeLinkButton()
1418 ->setHref('#')
1419 ->setOnClick($aOnClick)
1420 ->setTitle(
1421 sprintf(
1422 $lang->getLL('undoLastChange'),
1423 BackendUtility::calcAge(
1424 ($GLOBALS['EXEC_TIME'] - $undoButtonR['tstamp']),
1425 $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears')
1426 )
1427 )
1428 )
1429 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1430 'actions-edit-undo',
1431 Icon::SIZE_SMALL
1432 ));
1433 $buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1434 }
1435 if ($this->getNewIconMode($this->firstEl['table'], 'showHistory')) {
1436 $aOnClick = 'window.location.href=' .
1437 GeneralUtility::quoteJSvalue(
1438 BackendUtility::getModuleUrl(
1439 'record_history',
1440 array(
1441 'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1442 'returnUrl' => $this->R_URI,
1443 )
1444 )
1445 ) . '; return false;';
1446
1447 $historyButton = $buttonBar->makeLinkButton()
1448 ->setHref('#')
1449 ->setOnClick($aOnClick)
1450 ->setTitle('Open history of this record')
1451 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1452 'actions-document-history-open',
1453 Icon::SIZE_SMALL
1454 ));
1455 $buttonBar->addButton($historyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1456 }
1457 // If only SOME fields are shown in the form, this will link the user to the FULL form:
1458 if ($this->columnsOnly) {
1459 $columnsOnlyButton = $buttonBar->makeLinkButton()
1460 ->setHref($this->R_URI . '&columnsOnly=')
1461 ->setTitle($lang->getLL('editWholeRecord'))
1462 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1463 'actions-document-open',
1464 Icon::SIZE_SMALL
1465 ));
1466 $buttonBar->addButton($columnsOnlyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1467 }
1468 }
1469 }
1470 $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('TCEforms');
1471 $buttonBar->addButton($cshButton);
1472 $this->shortCutLink();
1473 $this->openInNewWindowLink();
1474 }
1475
1476 /**
1477 * Put together the various elements (buttons, selectors, form) into a table
1478 *
1479 * @param string $editForm HTML form.
1480 * @return string Composite HTML
1481 */
1482 public function compileForm($editForm)
1483 {
1484 $formContent = '
1485 <!-- EDITING FORM -->
1486 <form
1487 action="' . htmlspecialchars($this->R_URI) . '"
1488 method="post"
1489 enctype="multipart/form-data"
1490 name="editform"
1491 id="EditDocumentController"
1492 onsubmit="TBE_EDITOR.checkAndDoSubmit(1); return false;">
1493 ' . $editForm . '
1494
1495 <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
1496 <input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl) . '" />';
1497 if ($this->returnNewPageId) {
1498 $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1499 }
1500 $formContent .= '<input type="hidden" name="popViewId" value="' . htmlspecialchars($this->viewId) . '" />';
1501 if ($this->viewId_addParams) {
1502 $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams) . '" />';
1503 }
1504 $formContent .= '
1505 <input type="hidden" name="closeDoc" value="0" />
1506 <input type="hidden" name="doSave" value="0" />
1507 <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1508 <input type="hidden" name="_scrollPosition" value="" />';
1509 return $formContent;
1510 }
1511
1512 /**
1513 * Create shortcut icon
1514 */
1515 public function shortCutLink()
1516 {
1517 if ($this->returnUrl !== ExtensionManagementUtility::extRelPath('backend') . 'Resources/Private/Templates/Close.html') {
1518 $shortCutButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton();
1519 $shortCutButton->setModuleName($this->MCONF['name'])
1520 ->setGetVariables([
1521 'returnUrl',
1522 'edit',
1523 'defVals',
1524 'overrideVals',
1525 'columnsOnly',
1526 'returnNewPageId',
1527 'editRegularContentFromId',
1528 'noView']);
1529 $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton($shortCutButton);
1530 }
1531 }
1532
1533 /**
1534 * Creates open-in-window link
1535 */
1536 public function openInNewWindowLink()
1537 {
1538 $backendRelPath = ExtensionManagementUtility::extRelPath('backend');
1539 if ($this->returnUrl !== $backendRelPath . 'Resources/Private/Templates/Close.html') {
1540 $aOnClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript(
1541 array('returnUrl' => $backendRelPath . 'Resources/Private/Templates/Close.html')
1542 ))
1543 . ','
1544 . GeneralUtility::quoteJSvalue(md5($this->R_URI))
1545 . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
1546 $openInNewWindowButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()
1547 ->makeLinkButton()
1548 ->setHref('#')
1549 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow'))
1550 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-window-open', Icon::SIZE_SMALL))
1551 ->setOnClick($aOnClick);
1552 $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton(
1553 $openInNewWindowButton,
1554 ButtonBar::BUTTON_POSITION_RIGHT
1555 );
1556 }
1557 }
1558
1559 /***************************
1560 *
1561 * Localization stuff
1562 *
1563 ***************************/
1564 /**
1565 * Make selector box for creating new translation for a record or switching to edit the record in an existing
1566 * language.
1567 * Displays only languages which are available for the current page.
1568 *
1569 * @param string $table Table name
1570 * @param int $uid Uid for which to create a new language
1571 * @param int $pid Pid of the record
1572 */
1573 public function languageSwitch($table, $uid, $pid = null)
1574 {
1575 $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1576 $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1577 // Table editable and activated for languages?
1578 if ($this->getBackendUser()->check('tables_modify', $table)
1579 && $languageField
1580 && $transOrigPointerField && !$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']
1581 ) {
1582 if (is_null($pid)) {
1583 $row = BackendUtility::getRecord($table, $uid, 'pid');
1584 $pid = $row['pid'];
1585 }
1586 // Get all available languages for the page
1587 $langRows = $this->getLanguages($pid);
1588 // Page available in other languages than default language?
1589 if (is_array($langRows) && count($langRows) > 1) {
1590 $rowsByLang = array();
1591 $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1592 // Get record in current language
1593 $rowCurrent = BackendUtility::getLiveVersionOfRecord($table, $uid, $fetchFields);
1594 if (!is_array($rowCurrent)) {
1595 $rowCurrent = BackendUtility::getRecord($table, $uid, $fetchFields);
1596 }
1597 $currentLanguage = (int)$rowCurrent[$languageField];
1598 // Disabled for records with [all] language!
1599 if ($currentLanguage > -1) {
1600 // Get record in default language if needed
1601 if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1602 $rowsByLang[0] = BackendUtility::getLiveVersionOfRecord(
1603 $table,
1604 $rowCurrent[$transOrigPointerField],
1605 $fetchFields
1606 );
1607 if (!is_array($rowsByLang[0])) {
1608 $rowsByLang[0] = BackendUtility::getRecord(
1609 $table,
1610 $rowCurrent[$transOrigPointerField],
1611 $fetchFields
1612 );
1613 }
1614 } else {
1615 $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1616 }
1617 if ($rowCurrent[$transOrigPointerField] || $currentLanguage === 0) {
1618 // Get record in other languages to see what's already available
1619 $translations = $this->getDatabaseConnection()->exec_SELECTgetRows(
1620 $fetchFields,
1621 $table,
1622 'pid=' . (int)$pid . ' AND ' . $languageField . '>0' . ' AND ' . $transOrigPointerField . '=' . (int)$rowsByLang[0]['uid'] . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table)
1623 );
1624 foreach ($translations as $row) {
1625 $rowsByLang[$row[$languageField]] = $row;
1626 }
1627 }
1628 $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1629 $languageMenu->setIdentifier('_langSelector');
1630 $languageMenu->setLabel($this->getLanguageService()->sL(
1631 'LLL:EXT:lang/locallang_general.xlf:LGL.language',
1632 true
1633 ));
1634 foreach ($langRows as $lang) {
1635 if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
1636 $newTranslation = isset($rowsByLang[$lang['uid']]) ? '' : ' [' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.new', true) . ']';
1637 // Create url for creating a localized record
1638 if ($newTranslation) {
1639 $redirectUrl = BackendUtility::getModuleUrl('record_edit', array(
1640 'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
1641 'returnUrl' => $this->retUrl
1642 ));
1643 $href = BackendUtility::getLinkToDataHandlerAction(
1644 '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'],
1645 $redirectUrl
1646 );
1647 } else {
1648 $href = BackendUtility::getModuleUrl('record_edit', array(
1649 'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
1650 'returnUrl' => $this->retUrl
1651 ));
1652 }
1653 $menuItem = $languageMenu->makeMenuItem()
1654 ->setTitle($lang['title'] . $newTranslation)
1655 ->setHref($href);
1656 if ((int)$lang['uid'] === $currentLanguage) {
1657 $menuItem->setActive(true);
1658 }
1659 $languageMenu->addMenuItem($menuItem);
1660 }
1661 }
1662 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
1663 }
1664 }
1665 }
1666 }
1667
1668 /**
1669 * Redirects to FormEngine with new parameters to edit a just created localized record
1670 *
1671 * @param string $justLocalized String passed by GET &justLocalized=
1672 * @return void
1673 */
1674 public function localizationRedirect($justLocalized)
1675 {
1676 list($table, $orig_uid, $language) = explode(':', $justLocalized);
1677 if ($GLOBALS['TCA'][$table]
1678 && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1679 && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1680 ) {
1681 $localizedRecord = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
1682 'uid',
1683 $table,
1684 $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$language . ' AND '
1685 . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$orig_uid
1686 . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table)
1687 );
1688 if (is_array($localizedRecord)) {
1689 // Create parameters and finally run the classic page module for creating a new page translation
1690 $location = BackendUtility::getModuleUrl('record_edit', array(
1691 'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
1692 'returnUrl' => GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))
1693 ));
1694 HttpUtility::redirect($location);
1695 }
1696 }
1697 }
1698
1699 /**
1700 * Returns sys_language records available for record translations on given page.
1701 *
1702 * @param int $id Page id: If zero, the query will select all sys_language records from root level which are NOT
1703 * hidden. If set to another value, the query will select all sys_language records that has a
1704 * pages_language_overlay record on that page (and is not hidden, unless you are admin user)
1705 * @return array Language records including faked record for default language
1706 */
1707 public function getLanguages($id)
1708 {
1709 $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
1710 // Fallback non sprite-configuration
1711 if (preg_match('/\\.gif$/', $modSharedTSconfig['properties']['defaultLanguageFlag'])) {
1712 $modSharedTSconfig['properties']['defaultLanguageFlag'] = str_replace(
1713 '.gif',
1714 '',
1715 $modSharedTSconfig['properties']['defaultLanguageFlag']
1716 );
1717 }
1718 $languages = array(
1719 0 => array(
1720 'uid' => 0,
1721 'pid' => 0,
1722 'hidden' => 0,
1723 'title' => $modSharedTSconfig['properties']['defaultLanguageLabel'] !== ''
1724 ? $modSharedTSconfig['properties']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage') . ')'
1725 : $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage'),
1726 'flag' => $modSharedTSconfig['properties']['defaultLanguageFlag']
1727 )
1728 );
1729 $exQ = $this->getBackendUser()->isAdmin() ? '' : ' AND sys_language.hidden=0';
1730 if ($id) {
1731 $rows = $this->getDatabaseConnection()->exec_SELECTgetRows(
1732 'sys_language.*',
1733 'pages_language_overlay,sys_language',
1734 'pages_language_overlay.sys_language_uid=sys_language.uid
1735 AND pages_language_overlay.pid=' . (int)$id . BackendUtility::deleteClause('pages_language_overlay')
1736 . $exQ,
1737 'pages_language_overlay.sys_language_uid,
1738 sys_language.uid,
1739 sys_language.pid,
1740 sys_language.tstamp,
1741 sys_language.hidden,
1742 sys_language.title,
1743 sys_language.language_isocode,
1744 sys_language.static_lang_isocode,
1745 sys_language.flag',
1746 'sys_language.title'
1747 );
1748 } else {
1749 $rows = $this->getDatabaseConnection()->exec_SELECTgetRows(
1750 'sys_language.*',
1751 'sys_language',
1752 'sys_language.hidden=0',
1753 '',
1754 'sys_language.title'
1755 );
1756 }
1757 if ($rows) {
1758 foreach ($rows as $row) {
1759 $languages[$row['uid']] = $row;
1760 }
1761 }
1762 return $languages;
1763 }
1764
1765 /***************************
1766 *
1767 * Other functions
1768 *
1769 ***************************/
1770 /**
1771 * Fix $this->editconf if versioning applies to any of the records
1772 *
1773 * @param array|bool $mapArray Mapping between old and new ids if auto-versioning has been performed.
1774 * @return void
1775 */
1776 public function fixWSversioningInEditConf($mapArray = false)
1777 {
1778 // Traverse the editConf array
1779 if (is_array($this->editconf)) {
1780 // Tables:
1781 foreach ($this->editconf as $table => $conf) {
1782 if (is_array($conf) && $GLOBALS['TCA'][$table]) {
1783 // Traverse the keys/comments of each table (keys can be a commalist of uids)
1784 $newConf = array();
1785 foreach ($conf as $cKey => $cmd) {
1786 if ($cmd == 'edit') {
1787 // Traverse the ids:
1788 $ids = GeneralUtility::trimExplode(',', $cKey, true);
1789 foreach ($ids as $idKey => $theUid) {
1790 if (is_array($mapArray)) {
1791 if ($mapArray[$table][$theUid]) {
1792 $ids[$idKey] = $mapArray[$table][$theUid];
1793 }
1794 } else {
1795 // Default, look for versions in workspace for record:
1796 $calcPRec = $this->getRecordForEdit($table, $theUid);
1797 if (is_array($calcPRec)) {
1798 // Setting UID again if it had changed, eg. due to workspace versioning.
1799 $ids[$idKey] = $calcPRec['uid'];
1800 }
1801 }
1802 }
1803 // Add the possibly manipulated IDs to the new-build newConf array:
1804 $newConf[implode(',', $ids)] = $cmd;
1805 } else {
1806 $newConf[$cKey] = $cmd;
1807 }
1808 }
1809 // Store the new conf array:
1810 $this->editconf[$table] = $newConf;
1811 }
1812 }
1813 }
1814 }
1815
1816 /**
1817 * Get record for editing.
1818 *
1819 * @param string $table Table name
1820 * @param int $theUid Record UID
1821 * @return array Returns record to edit, FALSE if none
1822 */
1823 public function getRecordForEdit($table, $theUid)
1824 {
1825 // Fetch requested record:
1826 $reqRecord = BackendUtility::getRecord($table, $theUid, 'uid,pid');
1827 if (is_array($reqRecord)) {
1828 // If workspace is OFFLINE:
1829 if ($this->getBackendUser()->workspace != 0) {
1830 // Check for versioning support of the table:
1831 if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1832 // If the record is already a version of "something" pass it by.
1833 if ($reqRecord['pid'] == -1) {
1834 // (If it turns out not to be a version of the current workspace there will be trouble, but
1835 // that is handled inside TCEmain then and in the interface it would clearly be an error of
1836 // links if the user accesses such a scenario)
1837 return $reqRecord;
1838 } else {
1839 // The input record was online and an offline version must be found or made:
1840 // Look for version of this workspace:
1841 $versionRec = BackendUtility::getWorkspaceVersionOfRecord(
1842 $this->getBackendUser()->workspace,
1843 $table,
1844 $reqRecord['uid'],
1845 'uid,pid,t3ver_oid'
1846 );
1847 return is_array($versionRec) ? $versionRec : $reqRecord;
1848 }
1849 } else {
1850 // This means that editing cannot occur on this record because it was not supporting versioning
1851 // which is required inside an offline workspace.
1852 return false;
1853 }
1854 } else {
1855 // In ONLINE workspace, just return the originally requested record:
1856 return $reqRecord;
1857 }
1858 } else {
1859 // Return FALSE because the table/uid was not found anyway.
1860 return false;
1861 }
1862 }
1863
1864 /**
1865 * Function, which populates the internal editconf array with editing commands for all tt_content elements from
1866 * the normal column in normal language from the page pointed to by $this->editRegularContentFromId
1867 *
1868 * @return void
1869 * @deprecated since TYPO3 CMS 7, will be removed with TYPO3 CMS 8
1870 */
1871 public function editRegularContentFromId()
1872 {
1873 GeneralUtility::logDeprecatedFunction();
1874 $dbConnection = $this->getDatabaseConnection();
1875 $res = $dbConnection->exec_SELECTquery(
1876 'uid',
1877 'tt_content',
1878 'pid=' . (int)$this->editRegularContentFromId . BackendUtility::deleteClause('tt_content')
1879 . BackendUtility::versioningPlaceholderClause('tt_content') . ' AND colPos=0 AND sys_language_uid=0',
1880 '',
1881 'sorting'
1882 );
1883 if ($dbConnection->sql_num_rows($res)) {
1884 $ecUids = array();
1885 while ($ecRec = $dbConnection->sql_fetch_assoc($res)) {
1886 $ecUids[] = $ecRec['uid'];
1887 }
1888 $this->editconf['tt_content'][implode(',', $ecUids)] = 'edit';
1889 }
1890 $dbConnection->sql_free_result($res);
1891 }
1892
1893 /**
1894 * Populates the variables $this->storeArray, $this->storeUrl, $this->storeUrlMd5
1895 *
1896 * @return void
1897 * @see makeDocSel()
1898 */
1899 public function compileStoreDat()
1900 {
1901 $this->storeArray = GeneralUtility::compileSelectedGetVarsFromArray(
1902 'edit,defVals,overrideVals,columnsOnly,noView,editRegularContentFromId,workspace',
1903 $this->R_URL_getvars
1904 );
1905 $this->storeUrl = GeneralUtility::implodeArrayForUrl('', $this->storeArray);
1906 $this->storeUrlMd5 = md5($this->storeUrl);
1907 }
1908
1909 /**
1910 * Function used to look for configuration of buttons in the form: Fx. disabling buttons or showing them at various
1911 * positions.
1912 *
1913 * @param string $table The table for which the configuration may be specific
1914 * @param string $key The option for look for. Default is checking if the saveDocNew button should be displayed.
1915 * @return string Return value fetched from USER TSconfig
1916 */
1917 public function getNewIconMode($table, $key = 'saveDocNew')
1918 {
1919 $TSconfig = $this->getBackendUser()->getTSConfig('options.' . $key);
1920 $output = trim(isset($TSconfig['properties'][$table]) ? $TSconfig['properties'][$table] : $TSconfig['value']);
1921 return $output;
1922 }
1923
1924 /**
1925 * Handling the closing of a document
1926 *
1927 * @param int $code Close code: 0/1 will redirect to $this->retUrl, 3 will clear the docHandler (thus closing all
1928 * documents) and other values will call setDocument with ->retUrl
1929 * @return void
1930 */
1931 public function closeDocument($code = 0)
1932 {
1933 // If current document is found in docHandler,
1934 // then unset it, possibly unset it ALL and finally, write it to the session data
1935 if (isset($this->docHandler[$this->storeUrlMd5])) {
1936 // add the closing document to the recent documents
1937 $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
1938 if (!is_array($recentDocs)) {
1939 $recentDocs = array();
1940 }
1941 $closedDoc = $this->docHandler[$this->storeUrlMd5];
1942 $recentDocs = array_merge(array($this->storeUrlMd5 => $closedDoc), $recentDocs);
1943 if (count($recentDocs) > 8) {
1944 $recentDocs = array_slice($recentDocs, 0, 8);
1945 }
1946 // remove it from the list of the open documents
1947 unset($this->docHandler[$this->storeUrlMd5]);
1948 if ($code == '3') {
1949 $recentDocs = array_merge($this->docHandler, $recentDocs);
1950 $this->docHandler = array();
1951 }
1952 $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
1953 $this->getBackendUser()->pushModuleData('FormEngine', array($this->docHandler, $this->docDat[1]));
1954 BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
1955 }
1956 // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: (used by
1957 // other scripts, like wizard_add, to know which records was created or so...)
1958 if ($this->returnEditConf && $this->retUrl != BackendUtility::getModuleUrl('dummy')) {
1959 $this->retUrl .= '&returnEditConf=' . rawurlencode(json_encode($this->editconf));
1960 }
1961 // If code is NOT set OR set to 1, then make a header location redirect to $this->retUrl
1962 if (!$code || $code == 1) {
1963 HttpUtility::redirect($this->retUrl);
1964 } else {
1965 $this->setDocument('', $this->retUrl);
1966 }
1967 }
1968
1969 /**
1970 * Redirects to the document pointed to by $currentDocFromHandlerMD5 OR $retUrl (depending on some internal
1971 * calculations).
1972 * Most likely you will get a header-location redirect from this function.
1973 *
1974 * @param string $currentDocFromHandlerMD5 Pointer to the document in the docHandler array
1975 * @param string $retUrl Alternative/Default retUrl
1976 * @return void
1977 */
1978 public function setDocument($currentDocFromHandlerMD5 = '', $retUrl = '')
1979 {
1980 if ($retUrl === '') {
1981 return;
1982 }
1983 if (!$this->modTSconfig['properties']['disableDocSelector']
1984 && is_array($this->docHandler)
1985 && !empty($this->docHandler)
1986 ) {
1987 if (isset($this->docHandler[$currentDocFromHandlerMD5])) {
1988 $setupArr = $this->docHandler[$currentDocFromHandlerMD5];
1989 } else {
1990 $setupArr = reset($this->docHandler);
1991 }
1992 if ($setupArr[2]) {
1993 $sParts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
1994 $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
1995 }
1996 }
1997 HttpUtility::redirect($retUrl);
1998 }
1999
2000 /**
2001 * Injects the request object for the current request or subrequest
2002 *
2003 * @param ServerRequestInterface $request the current request
2004 * @param ResponseInterface $response
2005 * @return ResponseInterface the response with the content
2006 */
2007 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
2008 {
2009 BackendUtility::lockRecords();
2010
2011 // Preprocessing, storing data if submitted to
2012 $this->preInit();
2013
2014 // Checks, if a save button has been clicked (or the doSave variable is sent)
2015 if ($this->doProcessData()) {
2016 $this->processData();
2017 }
2018
2019 $this->init();
2020 $this->main();
2021
2022 $response->getBody()->write($this->moduleTemplate->renderContent());
2023 return $response;
2024 }
2025
2026 /**
2027 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
2028 */
2029 protected function getBackendUser()
2030 {
2031 return $GLOBALS['BE_USER'];
2032 }
2033
2034 /**
2035 * Returns LanguageService
2036 *
2037 * @return \TYPO3\CMS\Lang\LanguageService
2038 */
2039 protected function getLanguageService()
2040 {
2041 return $GLOBALS['LANG'];
2042 }
2043
2044 /**
2045 * Returns the database connection
2046 *
2047 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
2048 */
2049 protected function getDatabaseConnection()
2050 {
2051 return $GLOBALS['TYPO3_DB'];
2052 }
2053 }