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