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