[BUGFIX] additionalParameters for TCEMAIN.preview must handle arrays
[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 $GLOBALS['SOBE'] = $this;
413 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_alt_doc.xlf');
414 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
415 }
416
417 /**
418 * Get the SignalSlot dispatcher
419 *
420 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
421 */
422 protected function getSignalSlotDispatcher() {
423 if (!isset($this->signalSlotDispatcher)) {
424 $this->signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
425 }
426 return $this->signalSlotDispatcher;
427 }
428
429 /**
430 * Emits a signal after a function was executed
431 *
432 * @param string $signalName
433 */
434 protected function emitFunctionAfterSignal($signalName) {
435 $this->getSignalSlotDispatcher()->dispatch(__CLASS__, $signalName . 'After', array($this));
436 }
437
438 /**
439 * First initialization.
440 *
441 * @return void
442 */
443 public function preInit() {
444 if (GeneralUtility::_GP('justLocalized')) {
445 $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
446 }
447 // Setting GPvars:
448 $this->editconf = GeneralUtility::_GP('edit');
449 $this->defVals = GeneralUtility::_GP('defVals');
450 $this->overrideVals = GeneralUtility::_GP('overrideVals');
451 $this->columnsOnly = GeneralUtility::_GP('columnsOnly');
452 $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
453 $this->closeDoc = GeneralUtility::_GP('closeDoc');
454 $this->doSave = GeneralUtility::_GP('doSave');
455 $this->returnEditConf = GeneralUtility::_GP('returnEditConf');
456 $this->localizationMode = GeneralUtility::_GP('localizationMode');
457 $this->workspace = GeneralUtility::_GP('workspace');
458 $this->uc = GeneralUtility::_GP('uc');
459 // Setting override values as default if defVals does not exist.
460 if (!is_array($this->defVals) && is_array($this->overrideVals)) {
461 $this->defVals = $this->overrideVals;
462 }
463 // Setting return URL
464 $this->retUrl = $this->returnUrl ?: BackendUtility::getModuleUrl('dummy');
465 // Fix $this->editconf if versioning applies to any of the records
466 $this->fixWSversioningInEditConf();
467 // Make R_URL (request url) based on input GETvars:
468 $this->R_URL_parts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
469 $this->R_URL_getvars = GeneralUtility::_GET();
470 $this->R_URL_getvars['edit'] = $this->editconf;
471 // MAKE url for storing
472 $this->compileStoreDat();
473 // Get session data for the module:
474 $this->docDat = $this->getBackendUser()->getModuleData('FormEngine', 'ses');
475 $this->docHandler = $this->docDat[0];
476 // If a request for closing the document has been sent, act accordingly:
477 if ($this->closeDoc > 0) {
478 $this->closeDocument($this->closeDoc);
479 }
480 // If NO vars are sent to the script, try to read first document:
481 // Added !is_array($this->editconf) because editConf must not be set either.
482 // Anyways I can't figure out when this situation here will apply...
483 if (is_array($this->R_URL_getvars) && count($this->R_URL_getvars) < 2 && !is_array($this->editconf)) {
484 $this->setDocument($this->docDat[1]);
485 }
486
487 // Sets a temporary workspace, this request is based on
488 if ($this->workspace !== NULL) {
489 $this->getBackendUser()->setTemporaryWorkspace($this->workspace);
490 }
491
492 $this->emitFunctionAfterSignal(__FUNCTION__);
493 }
494
495 /**
496 * Detects, if a save command has been triggered.
497 *
498 * @return bool TRUE, then save the document (data submitted)
499 */
500 public function doProcessData() {
501 $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']);
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 $beUser = $this->getBackendUser();
512 // GPvars specifically for processing:
513 $control = GeneralUtility::_GP('control');
514 $this->data = GeneralUtility::_GP('data');
515 $this->cmd = GeneralUtility::_GP('cmd');
516 $this->mirror = GeneralUtility::_GP('mirror');
517 $this->cacheCmd = GeneralUtility::_GP('cacheCmd');
518 $this->redirect = GeneralUtility::_GP('redirect');
519 $this->returnNewPageId = GeneralUtility::_GP('returnNewPageId');
520 $this->vC = GeneralUtility::_GP('vC');
521 // See tce_db.php for relevate options here:
522 // Only options related to $this->data submission are included here.
523 /** @var $tce \TYPO3\CMS\Core\DataHandling\DataHandler */
524 $tce = GeneralUtility::makeInstance(DataHandler::class);
525 $tce->stripslashes_values = FALSE;
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'] && $this->vC != $beUser->veriCode() && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']) {
555 $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));
556 debug('Error: Referer host did not match with server host.');
557 } else {
558 // Perform the saving operation with TCEmain:
559 $tce->process_uploads($_FILES);
560 $tce->process_datamap();
561 $tce->process_cmdmap();
562 // If pages are being edited, we set an instruction about updating the page tree after this operation.
563 if ($tce->pagetreeNeedsRefresh && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))) {
564 BackendUtility::setUpdateSignal('updatePageTree');
565 }
566 // If there was saved any new items, load them:
567 if (!empty($tce->substNEWwithIDs_table)) {
568 // save the expanded/collapsed states for new inline records, if any
569 FormEngineUtility::updateInlineView($this->uc, $tce);
570 $newEditConf = array();
571 foreach ($this->editconf as $tableName => $tableCmds) {
572 $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
573 if (!empty($keys)) {
574 foreach ($keys as $key) {
575 $editId = $tce->substNEWwithIDs[$key];
576 // Check if the $editId isn't a child record of an IRRE action
577 if (!(is_array($tce->newRelatedIDs[$tableName]) && in_array($editId, $tce->newRelatedIDs[$tableName]))) {
578 // Translate new id to the workspace version:
579 if ($versionRec = BackendUtility::getWorkspaceVersionOfRecord($beUser->workspace, $tableName, $editId, 'uid')) {
580 $editId = $versionRec['uid'];
581 }
582 $newEditConf[$tableName][$editId] = 'edit';
583 }
584 // Traverse all new records and forge the content of ->editconf so we can continue to EDIT these records!
585 if ($tableName == 'pages' && $this->retUrl != BackendUtility::getModuleUrl('dummy') && $this->returnNewPageId) {
586 $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key];
587 }
588 }
589 } else {
590 $newEditConf[$tableName] = $tableCmds;
591 }
592 }
593 // Resetting editconf if newEditConf has values:
594 if (!empty($newEditConf)) {
595 $this->editconf = $newEditConf;
596 }
597 // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
598 $this->R_URL_getvars['edit'] = $this->editconf;
599 // Unsetting default values since we don't need them anymore.
600 unset($this->R_URL_getvars['defVals']);
601 // Re-compile the store* values since editconf changed...
602 $this->compileStoreDat();
603 }
604 // See if any records was auto-created as new versions?
605 if (!empty($tce->autoVersionIdMap)) {
606 $this->fixWSversioningInEditConf($tce->autoVersionIdMap);
607 }
608 // If a document is saved and a new one is created right after.
609 if (isset($_POST['_savedoknew']) && is_array($this->editconf)) {
610 // Finding the current table:
611 reset($this->editconf);
612 $nTable = key($this->editconf);
613 // Finding the first id, getting the records pid+uid
614 reset($this->editconf[$nTable]);
615 $nUid = key($this->editconf[$nTable]);
616 $nRec = BackendUtility::getRecord($nTable, $nUid, 'pid,uid');
617 // Setting a blank editconf array for a new record:
618 $this->editconf = array();
619 if ($this->getNewIconMode($nTable) == 'top') {
620 $this->editconf[$nTable][$nRec['pid']] = 'new';
621 } else {
622 $this->editconf[$nTable][-$nRec['uid']] = 'new';
623 }
624 // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
625 $this->R_URL_getvars['edit'] = $this->editconf;
626 // Re-compile the store* values since editconf changed...
627 $this->compileStoreDat();
628 }
629 // If a preview is requested
630 if (isset($_POST['_savedokview'])) {
631 // Get the first table and id of the data array from DataHandler
632 $table = reset(array_keys($this->data));
633 $id = reset(array_keys($this->data[$table]));
634 if (!MathUtility::canBeInterpretedAsInteger($id)) {
635 $id = $tce->substNEWwithIDs[$id];
636 }
637 // Store this information for later use
638 $this->previewData['table'] = $table;
639 $this->previewData['id'] = $id;
640 }
641 $tce->printLogErrorMessages(isset($_POST['_saveandclosedok']) || isset($_POST['_translation_savedok_x']) ? $this->retUrl : $this->R_URL_parts['path'] . '?' . GeneralUtility::implodeArrayForUrl('', $this->R_URL_getvars));
642 }
643 // || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED
644 // because if not, we just get that element re-listed as new. And we don't want that!
645 if (isset($_POST['_saveandclosedok']) || isset($_POST['_translation_savedok_x']) || $this->closeDoc < 0) {
646 $this->closeDocument(abs($this->closeDoc));
647 }
648 }
649
650 /**
651 * Initialize the normal module operation
652 *
653 * @return void
654 */
655 public function init() {
656 $beUser = $this->getBackendUser();
657 // Setting more GPvars:
658 $this->popViewId = GeneralUtility::_GP('popViewId');
659 $this->popViewId_addParams = GeneralUtility::_GP('popViewId_addParams');
660 $this->viewUrl = GeneralUtility::_GP('viewUrl');
661 $this->editRegularContentFromId = GeneralUtility::_GP('editRegularContentFromId');
662 $this->recTitle = GeneralUtility::_GP('recTitle');
663 $this->noView = GeneralUtility::_GP('noView');
664 $this->perms_clause = $beUser->getPagePermsClause(1);
665 // Set other internal variables:
666 $this->R_URL_getvars['returnUrl'] = $this->retUrl;
667 $this->R_URI = $this->R_URL_parts['path'] . '?' . ltrim(GeneralUtility::implodeArrayForUrl('', $this->R_URL_getvars), '&');
668 // Setting virtual document name
669 $this->MCONF['name'] = 'xMOD_alt_doc.php';
670
671 // Create an instance of the document template object
672 $this->doc = $GLOBALS['TBE_TEMPLATE'];
673 $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
674 $pageRenderer->addInlineLanguageLabelFile('EXT:lang/locallang_alt_doc.xlf');
675 $this->doc->setModuleTemplate('EXT:backend/Resources/Private/Templates/alt_doc.html');
676 $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;">';
677 // override the default jumpToUrl
678 $this->doc->JScodeArray['jumpToUrl'] = '
679 function jumpToUrl(URL,formEl) {
680 if (!TBE_EDITOR.isFormChanged()) {
681 window.location.href = URL;
682 } else if (formEl && formEl.type=="checkbox") {
683 formEl.checked = formEl.checked ? 0 : 1;
684 }
685 }
686 ';
687 // define the window size of the element browser
688 $popupWindowWidth = 700;
689 $popupWindowHeight = 750;
690 $popupWindowSize = trim($beUser->getTSConfigVal('options.popupWindowSize'));
691 if (!empty($popupWindowSize)) {
692 list($popupWindowWidth, $popupWindowHeight) = GeneralUtility::intExplode('x', $popupWindowSize);
693 }
694 $t3Configuration = array(
695 'PopupWindow' => array(
696 'width' => $popupWindowWidth,
697 'height' => $popupWindowHeight
698 ),
699 );
700 $javascript = '
701 TYPO3.configuration = ' . json_encode($t3Configuration) . ';
702 // Object: TS:
703 // passwordDummy and decimalSign are used by tbe_editor.js and have to be declared here as
704 // TS object overwrites the object declared in tbe_editor.js
705 function typoSetup() { //
706 this.uniqueID = "";
707 this.passwordDummy = "********";
708 this.PATH_typo3 = "";
709 this.decimalSign = ".";
710 }
711 var TS = new typoSetup();
712
713 // Info view:
714 function launchView(table,uid,bP) { //
715 var backPath= bP ? bP : "";
716 var thePreviewWindow = window.open(
717 backPath+' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('show_item') . '&table=') . ' + encodeURIComponent(table) + "&uid=" + encodeURIComponent(uid),
718 "ShowItem" + TS.uniqueID,
719 "height=300,width=410,status=0,menubar=0,resizable=0,location=0,directories=0,scrollbars=1,toolbar=0"
720 );
721 if (thePreviewWindow && thePreviewWindow.focus) {
722 thePreviewWindow.focus();
723 }
724 }
725 function deleteRecord(table,id,url) { //
726 window.location.href = ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&cmd[') . '+table+"]["+id+"][delete]=1&redirect="+escape(url)+"&vC=' . $beUser->veriCode() . '&prErr=1&uPT=1";
727 }
728 ';
729
730 $previewCode = isset($_POST['_savedokview']) && $this->popViewId ? $this->generatePreviewCode() : '';
731
732 $this->doc->JScode = $this->doc->wrapScriptTags($javascript . $previewCode);
733 // Setting up the context sensitive menu:
734 $this->doc->getContextMenuCode();
735 $this->doc->bodyTagAdditions = 'onload="window.scrollTo(0,' . MathUtility::forceIntegerInRange(GeneralUtility::_GP('_scrollPosition'), 0, 10000) . ');"';
736
737 $this->emitFunctionAfterSignal(__FUNCTION__);
738 }
739
740 /**
741 * @return string
742 */
743 protected function generatePreviewCode() {
744 $currentPageId = MathUtility::convertToPositiveInteger($this->popViewId);
745 $table = $this->previewData['table'];
746 $recordId = $this->previewData['id'];
747
748 $pageTsConfig = BackendUtility::getPagesTSconfig($currentPageId);
749 $previewConfiguration = isset($pageTsConfig['TCEMAIN.']['preview.'][$table . '.'])
750 ? $pageTsConfig['TCEMAIN.']['preview.'][$table . '.']
751 : array();
752
753 $recordArray = BackendUtility::getRecord($table, $recordId);
754
755 // find the right preview page id
756 $previewPageId = 0;
757 if (isset($previewConfiguration['previewPageId'])) {
758 $previewPageId = $previewConfiguration['previewPageId'];
759 }
760 // if no preview page was configured
761 if (!$previewPageId) {
762 $rootPageData = NULL;
763 $rootLine = BackendUtility::BEgetRootLine($currentPageId);
764 $currentPage = reset($rootLine);
765 if ((int)$currentPage['doktype'] === PageRepository::DOKTYPE_DEFAULT) {
766 // try the current page
767 $previewPageId = $currentPageId;
768 } else {
769 // or search for the root page
770 foreach ($rootLine as $page) {
771 if ($page['is_siteroot']) {
772 $rootPageData = $page;
773 break;
774 }
775 }
776 $previewPageId = isset($rootPageData)
777 ? (int)$rootPageData['uid']
778 : $currentPageId;
779 }
780 }
781
782 $linkParameters = [
783 'no_cache' => 1,
784 ];
785
786 // language handling
787 $languageField = isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
788 ? $GLOBALS['TCA'][$table]['ctrl']['languageField']
789 : '';
790 if ($languageField && !empty($recordArray[$languageField])) {
791 $l18nPointer = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
792 ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
793 : '';
794 if (
795 $l18nPointer && !empty($recordArray[$l18nPointer])
796 && isset($previewConfiguration['useDefaultLanguageRecord'])
797 && !$previewConfiguration['useDefaultLanguageRecord']
798 ) {
799 // use parent record
800 $recordId = $recordArray[$l18nPointer];
801 }
802 $linkParameters['L'] = $recordArray[$languageField];
803 }
804
805 // map record data to GET parameters
806 if (isset($previewConfiguration['fieldToParameterMap.'])) {
807 foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
808 $value = $recordArray[$field];
809 if ($field === 'uid') {
810 $value = $recordId;
811 }
812 $linkParameters[$parameterName] = $value;
813 }
814 }
815
816 // add/override parameters by configuration
817 if (isset($previewConfiguration['additionalGetParameters.'])) {
818 $additionalGetParameters = [];
819 $this->parseAdditionalGetParameters($additionalGetParameters, $previewConfiguration['additionalGetParameters.']);
820 $linkParameters = array_replace($linkParameters, $additionalGetParameters);
821 }
822
823 $this->popViewId = $previewPageId;
824 $this->popViewId_addParams = GeneralUtility::implodeArrayForUrl('', $linkParameters, '', FALSE, TRUE);
825
826 $previewPageRootline = BackendUtility::BEgetRootLine($this->popViewId);
827 return '
828 if (window.opener) {
829 '
830 . BackendUtility::viewOnClick($this->popViewId, '', $previewPageRootline, '', $this->viewUrl, $this->popViewId_addParams, FALSE)
831 . '
832 } else {
833 '
834 . BackendUtility::viewOnClick($this->popViewId, '', $previewPageRootline, '', $this->viewUrl, $this->popViewId_addParams)
835 . '
836 }';
837 }
838
839 /**
840 * Migrates a set of (possibly nested) GET parameters in TypoScript syntax to a plain array
841 *
842 * This basically removes the trailing dots of sub-array keys in TypoScript.
843 * The result can be used to create a query string with GeneralUtility::implodeArrayForUrl().
844 *
845 * @param array $parameters Should be an empty array by default
846 * @param array $typoScript The TypoScript configuration
847 */
848 protected function parseAdditionalGetParameters(array &$parameters, array $typoScript) {
849 foreach ($typoScript as $key => $value) {
850 if (is_array($value)) {
851 $key = rtrim($key, '.');
852 $parameters[$key] = [];
853 $this->parseAdditionalGetParameters($parameters[$key], $value);
854 } else {
855 $parameters[$key] = $value;
856 }
857 }
858 }
859
860 /**
861 * Main module operation
862 *
863 * @return void
864 */
865 public function main() {
866 $body = '';
867 // Begin edit:
868 if (is_array($this->editconf)) {
869 /** @var FormResultCompiler formResultCompiler */
870 $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
871
872 if ($this->editRegularContentFromId) {
873 $this->editRegularContentFromId();
874 }
875 // Creating the editing form, wrap it with buttons, document selector etc.
876 $editForm = $this->makeEditForm();
877 if ($editForm) {
878 $this->firstEl = reset($this->elementsData);
879 // Checking if the currently open document is stored in the list of "open documents" - if not, then add it:
880 if (($this->docDat[1] !== $this->storeUrlMd5 || !isset($this->docHandler[$this->storeUrlMd5])) && !$this->dontStoreDocumentRef) {
881 $this->docHandler[$this->storeUrlMd5] = array($this->storeTitle, $this->storeArray, $this->storeUrl, $this->firstEl);
882 $this->getBackendUser()->pushModuleData('FormEngine', array($this->docHandler, $this->storeUrlMd5));
883 BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
884 }
885 // Module configuration
886 $this->modTSconfig = $this->viewId ? BackendUtility::getModTSconfig($this->viewId, 'mod.xMOD_alt_doc') : array();
887 $body = $this->formResultCompiler->JStop();
888 $body .= $this->compileForm($editForm);
889 $body .= $this->formResultCompiler->printNeededJSFunctions();
890 }
891 }
892 // Access check...
893 // The page will show only if there is a valid page and if this page may be viewed by the user
894 $this->pageinfo = BackendUtility::readPageAccess($this->viewId, $this->perms_clause);
895 // Setting up the buttons and markers for docheader
896 $docHeaderButtons = $this->getButtons();
897 $markers = array(
898 'LANGSELECTOR' => $this->langSelector(),
899 'CSH' => $docHeaderButtons['csh'],
900 'CONTENT' => $body
901 );
902 // Build the <body> for the module
903 $this->content = $this->doc->startPage('TYPO3 Edit Document');
904 $this->content .= $this->doc->moduleBody($this->pageinfo, $docHeaderButtons, $markers);
905 $this->content .= $this->doc->endPage();
906 $this->content = $this->doc->insertStylesAndJS($this->content);
907 }
908
909 /**
910 * Outputting the accumulated content to screen
911 *
912 * @return void
913 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8
914 */
915 public function printContent() {
916 GeneralUtility::logDeprecatedFunction();
917 echo $this->content;
918 }
919
920 /***************************
921 *
922 * Sub-content functions, rendering specific parts of the module content.
923 *
924 ***************************/
925 /**
926 * Creates the editing form with FormEnigne, based on the input from GPvars.
927 *
928 * @return string HTML form elements wrapped in tables
929 */
930 public function makeEditForm() {
931 // Initialize variables:
932 $this->elementsData = array();
933 $this->errorC = 0;
934 $this->newC = 0;
935 $editForm = '';
936 $trData = NULL;
937 $beUser = $this->getBackendUser();
938 // Traverse the GPvar edit array
939 // Tables:
940 foreach ($this->editconf as $table => $conf) {
941 if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
942 // Traverse the keys/comments of each table (keys can be a commalist of uids)
943 foreach ($conf as $cKey => $command) {
944 if ($command == 'edit' || $command == 'new') {
945 // Get the ids:
946 $ids = GeneralUtility::trimExplode(',', $cKey, TRUE);
947 // Traverse the ids:
948 foreach ($ids as $theUid) {
949
950 // Don't save this document title in the document selector if the document is new.
951 if ($command === 'new') {
952 $this->dontStoreDocumentRef = 1;
953 }
954
955 /** @var TcaDatabaseRecord $formDataGroup */
956 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
957 /** @var FormDataCompiler $formDataCompiler */
958 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
959 /** @var NodeFactory $nodeFactory */
960 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
961
962 try {
963 // Reset viewId - it should hold data of last entry only
964 $this->viewId = 0;
965 $this->viewId_addParams = '';
966
967 $formDataCompilerInput = [
968 'tableName' => $table,
969 'vanillaUid' => (int)$theUid,
970 'command' => $command,
971 'returnUrl' => $this->R_URI,
972 ];
973 if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
974 $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
975 }
976
977 $formData = $formDataCompiler->compile($formDataCompilerInput);
978
979 // Set this->viewId if possible
980 if ($command === 'new' && $table !== 'pages' && !empty($formData['parentPageRow']['uid'])) {
981 $this->viewId = $formData['parentPageRow']['uid'];
982 } else {
983 if ($table == 'pages') {
984 $this->viewId = $formData['databaseRow']['uid'];
985 } elseif (!empty($formData['parentPageRow']['uid'])) {
986 $this->viewId = $formData['parentPageRow']['uid'];
987 // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
988 if (!empty($formData['vanillaTableTca']['ctrl']['languageField'])
989 && is_array($formData['databaseRow'][$formData['vanillaTableTca']['ctrl']['languageField']])
990 && $formData['databaseRow'][$formData['vanillaTableTca']['ctrl']['languageField']][0] > 0
991 ) {
992 $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['vanillaTableTca']['ctrl']['languageField']][0];
993 }
994 }
995 }
996
997 // Determine if delete button can be shown
998 $deleteAccess = FALSE;
999 if ($command === 'edit') {
1000 $permission = $formData['userPermissionOnPage'];
1001 if ($formData['tableName'] === 'pages') {
1002 $deleteAccess = $permission & Permission::PAGE_DELETE ? TRUE : FALSE;
1003 } else {
1004 $deleteAccess = $permission & Permission::CONTENT_EDIT ? TRUE : FALSE;
1005 }
1006 }
1007
1008 // Display "is-locked" message:
1009 if ($command === 'edit') {
1010 $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
1011 if ($lockInfo) {
1012 /** @var $flashMessage \TYPO3\CMS\Core\Messaging\FlashMessage */
1013 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, htmlspecialchars($lockInfo['msg']), '', FlashMessage::WARNING);
1014 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
1015 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1016 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
1017 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1018 $defaultFlashMessageQueue->enqueue($flashMessage);
1019 }
1020 }
1021
1022 // Record title
1023 if (!$this->storeTitle) {
1024 $this->storeTitle = $this->recTitle
1025 ? htmlspecialchars($this->recTitle)
1026 : BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($formData['databaseRow']), TRUE);
1027 }
1028
1029 $this->elementsData[] = array(
1030 'table' => $table,
1031 'uid' => $formData['databaseRow']['uid'],
1032 'pid' => $formData['databaseRow']['pid'],
1033 'cmd' => $command,
1034 'deleteAccess' => $deleteAccess
1035 );
1036
1037 if ($command !== 'new') {
1038 BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
1039 }
1040
1041 // Set list if only specific fields should be rendered. This will trigger
1042 // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1043 if ($this->columnsOnly) {
1044 if (is_array($this->columnsOnly)) {
1045 $formData['fieldListToRender'] = $this->columnsOnly[$table];
1046 } else {
1047 $formData['fieldListToRender'] = $this->columnsOnly;
1048 }
1049 }
1050
1051 $formData['renderType'] = 'outerWrapContainer';
1052 $formResult = $nodeFactory->create($formData)->render();
1053
1054 $html = $formResult['html'];
1055
1056 $formResult['html'] = '';
1057 $formResult['doSaveFieldName'] = 'doSave';
1058
1059 // @todo: Put all the stuff into FormEngine as final "compiler" class
1060 // @todo: This is done here for now to not rewrite JStop()
1061 // @todo: and printNeededJSFunctions() now
1062 $this->formResultCompiler->mergeResult($formResult);
1063
1064 // Seems the pid is set as hidden field (again) at end?!
1065 if ($command == 'new') {
1066 // @todo: looks ugly
1067 $html .= LF
1068 . '<input type="hidden"'
1069 . ' name="data[' . $table . '][' . $formData['databaseRow']['uid'] . '][pid]"'
1070 . ' value="' . $formData['databaseRow']['pid'] . '" />';
1071 $this->newC++;
1072 }
1073
1074 $editForm .= $html;
1075
1076 } catch (AccessDeniedException $e) {
1077 $this->errorC++;
1078 // Try to fetch error message from "recordInternals" be user object
1079 // @todo: This construct should be logged and localized and de-uglified
1080 $message = $beUser->errorMsg;
1081 if (empty($message)) {
1082 // Create message from exception.
1083 $message = $e->getMessage() . ' ' . $e->getCode();
1084 }
1085 $editForm .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noEditPermission', TRUE)
1086 . '<br /><br />' . htmlspecialchars($message) . '<br /><br />';
1087 }
1088 } // End of for each uid
1089 }
1090 }
1091 }
1092 }
1093 return $editForm;
1094 }
1095
1096 /**
1097 * Create the panel of buttons for submitting the form or otherwise perform operations.
1098 *
1099 * @return array All available buttons as an assoc. array
1100 */
1101 protected function getButtons() {
1102 $lang = $this->getLanguageService();
1103 $buttons = array(
1104 'save' => '',
1105 'save_view' => '',
1106 'save_new' => '',
1107 'save_close' => '',
1108 'close' => '',
1109 'delete' => '',
1110 'undo' => '',
1111 'history' => '',
1112 'columns_only' => '',
1113 'csh' => '',
1114 'translation_save' => '',
1115 'translation_saveclear' => ''
1116 );
1117 /** @var IconFactory $iconFactory */
1118 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
1119 // Render SAVE type buttons:
1120 // The action of each button is decided by its name attribute. (See doProcessData())
1121 if (!$this->errorC && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']) {
1122 // SAVE button:
1123 $buttons['save'] = '<button name="_savedok" class="c-inputButton" value="1" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDoc', TRUE) . '">'
1124 . $iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL)->render()
1125 . '</button>';
1126 // SAVE / VIEW button:
1127 if ($this->viewId && !$this->noView && $this->getNewIconMode($this->firstEl['table'], 'saveDocView')) {
1128 $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
1129 if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1130 $excludeDokTypes = GeneralUtility::intExplode(',', $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'], TRUE);
1131 } else {
1132 // exclude sysfolders, spacers and recycler by default
1133 $excludeDokTypes = array(PageRepository::DOKTYPE_RECYCLER, PageRepository::DOKTYPE_SYSFOLDER, PageRepository::DOKTYPE_SPACER);
1134 }
1135 if (!in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, TRUE) || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'].'.']['previewPageId'])) {
1136 $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\');">'
1137 . $iconFactory->getIcon('actions-document-save-view', Icon::SIZE_SMALL)->render()
1138 . '</button>';
1139 }
1140 }
1141 // SAVE / NEW button:
1142 if (count($this->elementsData) === 1 && $this->getNewIconMode($this->firstEl['table'])) {
1143 $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) . '">'
1144 . $iconFactory->getIcon('actions-document-save-new', Icon::SIZE_SMALL)->render()
1145 . '</button>';
1146 }
1147 // SAVE / CLOSE
1148 $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) . '">'
1149 . $iconFactory->getIcon('actions-document-save-close', Icon::SIZE_SMALL)->render()
1150 . '</button>';
1151 // FINISH TRANSLATION / SAVE / CLOSE
1152 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation']) {
1153 $buttons['translation_save'] = '<button name="_translation_savedok" class="c-inputButton" value="1" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDoc', TRUE) . '">'
1154 . $iconFactory->getIcon('actions-document-save-translation', Icon::SIZE_SMALL)->render()
1155 . '</button>';
1156 $buttons['translation_saveclear'] = '<button name="_translation_savedokclear" class="c-inputButton" value="1" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDocClear', TRUE) . '">'
1157 . $iconFactory->getIcon('actions-document-save-cleartranslationcache', Icon::SIZE_SMALL)->render()
1158 . '</button>';
1159 }
1160 }
1161 // CLOSE button:
1162 $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>';
1163 // DELETE + UNDO buttons:
1164 if (!$this->errorC && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly'] && count($this->elementsData) === 1) {
1165 if ($this->firstEl['cmd'] != 'new' && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])) {
1166 // Delete:
1167 if ($this->firstEl['deleteAccess'] && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly'] && !$this->getNewIconMode($this->firstEl['table'], 'disableDelete')) {
1168 $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>';
1169 }
1170 // Undo:
1171 $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');
1172 if ($undoButtonR = $this->getDatabaseConnection()->sql_fetch_assoc($undoRes)) {
1173 $aOnClick = 'window.location.href=' .
1174 GeneralUtility::quoteJSvalue(
1175 BackendUtility::getModuleUrl(
1176 'record_history',
1177 array(
1178 'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1179 'revert' => 'ALL_FIELDS',
1180 'sumUp' => -1,
1181 'returnUrl' => $this->R_URI,
1182 )
1183 )
1184 ) . '; return false;';
1185 $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>';
1186 }
1187 if ($this->getNewIconMode($this->firstEl['table'], 'showHistory')) {
1188 $aOnClick = 'window.location.href=' .
1189 GeneralUtility::quoteJSvalue(
1190 BackendUtility::getModuleUrl(
1191 'record_history',
1192 array(
1193 'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1194 'returnUrl' => $this->R_URI,
1195 )
1196 )
1197 ) . '; return false;';
1198 $buttons['history'] = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $this->iconFactory->getIcon('actions-document-history-open', Icon::SIZE_SMALL)->render() . '</a>';
1199 }
1200 // If only SOME fields are shown in the form, this will link the user to the FULL form:
1201 if ($this->columnsOnly) {
1202 $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>';
1203 }
1204 }
1205 }
1206 // add the CSH icon
1207 $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'TCEforms');
1208 $buttons['shortcut'] = $this->shortCutLink();
1209 $buttons['open_in_new_window'] = $this->openInNewWindowLink();
1210
1211 return $buttons;
1212 }
1213
1214 /**
1215 * Returns the language switch/selector for editing,
1216 * show only when a single record is edited
1217 * - multiple records are too confusing
1218 *
1219 * @return string The HTML
1220 */
1221 public function langSelector() {
1222 $langSelector = '';
1223 if (count($this->elementsData) === 1) {
1224 $langSelector = $this->languageSwitch($this->firstEl['table'], $this->firstEl['uid'], $this->firstEl['pid']);
1225 }
1226 return $langSelector;
1227 }
1228
1229 /**
1230 * Put together the various elements (buttons, selectors, form) into a table
1231 *
1232 * @param string $editForm HTML form.
1233 * @return string Composite HTML
1234 */
1235 public function compileForm($editForm) {
1236 $formContent = '
1237 <!-- EDITING FORM -->
1238 ' . $editForm . '
1239
1240 <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
1241 <input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl) . '" />';
1242 if ($this->returnNewPageId) {
1243 $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1244 }
1245 $formContent .= '<input type="hidden" name="popViewId" value="' . htmlspecialchars($this->viewId) . '" />';
1246 if ($this->viewId_addParams) {
1247 $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams) . '" />';
1248 }
1249 $formContent .= '
1250 <input type="hidden" name="closeDoc" value="0" />
1251 <input type="hidden" name="doSave" value="0" />
1252 <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1253 <input type="hidden" name="_scrollPosition" value="" />';
1254 return $formContent;
1255 }
1256
1257 /**
1258 * Create shortcut icon
1259 *
1260 * @return string
1261 */
1262 public function shortCutLink() {
1263 if ($this->returnUrl === 'sysext/backend/Resources/Private/Templates/Close.html' || !$this->getBackendUser()->mayMakeShortcut()) {
1264 return '';
1265 }
1266 return $this->doc->makeShortcutIcon('returnUrl,edit,defVals,overrideVals,columnsOnly,returnNewPageId,editRegularContentFromId,noView', '', $this->MCONF['name'], 1);
1267 }
1268
1269 /**
1270 * Creates open-in-window link
1271 *
1272 * @return string
1273 */
1274 public function openInNewWindowLink() {
1275 if ($this->returnUrl === 'sysext/backend/Resources/Private/Templates/Close.html') {
1276 return '';
1277 }
1278 $aOnClick = '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;';
1279 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>';
1280 }
1281
1282 /***************************
1283 *
1284 * Localization stuff
1285 *
1286 ***************************/
1287 /**
1288 * Make selector box for creating new translation for a record or switching to edit the record in an existing language.
1289 * Displays only languages which are available for the current page.
1290 *
1291 * @param string $table Table name
1292 * @param int $uid Uid for which to create a new language
1293 * @param int $pid Pid of the record
1294 * @return string <select> HTML element (if there were items for the box anyways...)
1295 */
1296 public function languageSwitch($table, $uid, $pid = NULL) {
1297 $content = '';
1298 $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1299 $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1300 // Table editable and activated for languages?
1301 if ($this->getBackendUser()->check('tables_modify', $table) && $languageField && $transOrigPointerField && !$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']) {
1302 if (is_null($pid)) {
1303 $row = BackendUtility::getRecord($table, $uid, 'pid');
1304 $pid = $row['pid'];
1305 }
1306 // Get all avalibale languages for the page
1307 $langRows = $this->getLanguages($pid);
1308 // Page available in other languages than default language?
1309 if (is_array($langRows) && count($langRows) > 1) {
1310 $rowsByLang = array();
1311 $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1312 // Get record in current language
1313 $rowCurrent = BackendUtility::getLiveVersionOfRecord($table, $uid, $fetchFields);
1314 if (!is_array($rowCurrent)) {
1315 $rowCurrent = BackendUtility::getRecord($table, $uid, $fetchFields);
1316 }
1317 $currentLanguage = $rowCurrent[$languageField];
1318 // Disabled for records with [all] language!
1319 if ($currentLanguage > -1) {
1320 // Get record in default language if needed
1321 if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1322 $rowsByLang[0] = BackendUtility::getLiveVersionOfRecord($table, $rowCurrent[$transOrigPointerField], $fetchFields);
1323 if (!is_array($rowsByLang[0])) {
1324 $rowsByLang[0] = BackendUtility::getRecord($table, $rowCurrent[$transOrigPointerField], $fetchFields);
1325 }
1326 } else {
1327 $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1328 }
1329 if ($rowCurrent[$transOrigPointerField] || $currentLanguage === '0') {
1330 // Get record in other languages to see what's already available
1331 $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));
1332 foreach ($translations as $row) {
1333 $rowsByLang[$row[$languageField]] = $row;
1334 }
1335 }
1336 $langSelItems = array();
1337 foreach ($langRows as $lang) {
1338 if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
1339 $newTranslation = isset($rowsByLang[$lang['uid']]) ? '' : ' [' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.new', TRUE) . ']';
1340 // Create url for creating a localized record
1341 if ($newTranslation) {
1342 $redirectUrl = BackendUtility::getModuleUrl('record_edit', array(
1343 'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
1344 'returnUrl' => $this->retUrl
1345 ));
1346 $href = $this->doc->issueCommand('&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'], $redirectUrl);
1347 } else {
1348 $href = BackendUtility::getModuleUrl('record_edit', array(
1349 'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
1350 'returnUrl' => $this->retUrl
1351 ));
1352 }
1353 $langSelItems[$lang['uid']] = '
1354 <option value="' . htmlspecialchars($href) . '"' . ($currentLanguage == $lang['uid'] ? ' selected="selected"' : '') . '>' . htmlspecialchars(($lang['title'] . $newTranslation)) . '</option>';
1355 }
1356 }
1357 // If any languages are left, make selector:
1358 if (count($langSelItems) > 1) {
1359 $onChange = 'if(this.options[this.selectedIndex].value){window.location.href=(this.options[this.selectedIndex].value);}';
1360 $content = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_general.xlf:LGL.language', TRUE) . ' <select name="_langSelector" onchange="' . htmlspecialchars($onChange) . '">
1361 ' . implode('', $langSelItems) . '
1362 </select>';
1363 }
1364 }
1365 }
1366 }
1367 return $content;
1368 }
1369
1370 /**
1371 * Redirects to FormEngine with new parameters to edit a just created localized record
1372 *
1373 * @param string $justLocalized String passed by GET &justLocalized=
1374 * @return void
1375 */
1376 public function localizationRedirect($justLocalized) {
1377 list($table, $orig_uid, $language) = explode(':', $justLocalized);
1378 if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
1379 $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));
1380 if (is_array($localizedRecord)) {
1381 // Create parameters and finally run the classic page module for creating a new page translation
1382 $location = BackendUtility::getModuleUrl('record_edit', array(
1383 'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
1384 'returnUrl' => GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))
1385 ));
1386 HttpUtility::redirect($location);
1387 }
1388 }
1389 }
1390
1391 /**
1392 * Returns sys_language records available for record translations on given page.
1393 *
1394 * @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)
1395 * @return array Language records including faked record for default language
1396 */
1397 public function getLanguages($id) {
1398 $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
1399 // Fallback non sprite-configuration
1400 if (preg_match('/\\.gif$/', $modSharedTSconfig['properties']['defaultLanguageFlag'])) {
1401 $modSharedTSconfig['properties']['defaultLanguageFlag'] = str_replace('.gif', '', $modSharedTSconfig['properties']['defaultLanguageFlag']);
1402 }
1403 $languages = array(
1404 0 => array(
1405 'uid' => 0,
1406 'pid' => 0,
1407 'hidden' => 0,
1408 'title' => $modSharedTSconfig['properties']['defaultLanguageLabel'] !== ''
1409 ? $modSharedTSconfig['properties']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sl('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage') . ')'
1410 : $this->getLanguageService()->sl('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage'),
1411 'flag' => $modSharedTSconfig['properties']['defaultLanguageFlag']
1412 )
1413 );
1414 $exQ = $this->getBackendUser()->isAdmin() ? '' : ' AND sys_language.hidden=0';
1415 if ($id) {
1416 $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');
1417 } else {
1418 $rows = $this->getDatabaseConnection()->exec_SELECTgetRows('sys_language.*', 'sys_language', 'sys_language.hidden=0', '', 'sys_language.title');
1419 }
1420 if ($rows) {
1421 foreach ($rows as $row) {
1422 $languages[$row['uid']] = $row;
1423 }
1424 }
1425 return $languages;
1426 }
1427
1428 /***************************
1429 *
1430 * Other functions
1431 *
1432 ***************************/
1433 /**
1434 * Fix $this->editconf if versioning applies to any of the records
1435 *
1436 * @param array|bool $mapArray Mapping between old and new ids if auto-versioning has been performed.
1437 * @return void
1438 */
1439 public function fixWSversioningInEditConf($mapArray = FALSE) {
1440 // Traverse the editConf array
1441 if (is_array($this->editconf)) {
1442 // Tables:
1443 foreach ($this->editconf as $table => $conf) {
1444 if (is_array($conf) && $GLOBALS['TCA'][$table]) {
1445 // Traverse the keys/comments of each table (keys can be a commalist of uids)
1446 $newConf = array();
1447 foreach ($conf as $cKey => $cmd) {
1448 if ($cmd == 'edit') {
1449 // Traverse the ids:
1450 $ids = GeneralUtility::trimExplode(',', $cKey, TRUE);
1451 foreach ($ids as $idKey => $theUid) {
1452 if (is_array($mapArray)) {
1453 if ($mapArray[$table][$theUid]) {
1454 $ids[$idKey] = $mapArray[$table][$theUid];
1455 }
1456 } else {
1457 // Default, look for versions in workspace for record:
1458 $calcPRec = $this->getRecordForEdit($table, $theUid);
1459 if (is_array($calcPRec)) {
1460 // Setting UID again if it had changed, eg. due to workspace versioning.
1461 $ids[$idKey] = $calcPRec['uid'];
1462 }
1463 }
1464 }
1465 // Add the possibly manipulated IDs to the new-build newConf array:
1466 $newConf[implode(',', $ids)] = $cmd;
1467 } else {
1468 $newConf[$cKey] = $cmd;
1469 }
1470 }
1471 // Store the new conf array:
1472 $this->editconf[$table] = $newConf;
1473 }
1474 }
1475 }
1476 }
1477
1478 /**
1479 * Get record for editing.
1480 *
1481 * @param string $table Table name
1482 * @param int $theUid Record UID
1483 * @return array Returns record to edit, FALSE if none
1484 */
1485 public function getRecordForEdit($table, $theUid) {
1486 // Fetch requested record:
1487 $reqRecord = BackendUtility::getRecord($table, $theUid, 'uid,pid');
1488 if (is_array($reqRecord)) {
1489 // If workspace is OFFLINE:
1490 if ($this->getBackendUser()->workspace != 0) {
1491 // Check for versioning support of the table:
1492 if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1493 // If the record is already a version of "something" pass it by.
1494 if ($reqRecord['pid'] == -1) {
1495 // (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)
1496 return $reqRecord;
1497 } else {
1498 // The input record was online and an offline version must be found or made:
1499 // Look for version of this workspace:
1500 $versionRec = BackendUtility::getWorkspaceVersionOfRecord($this->getBackendUser()->workspace, $table, $reqRecord['uid'], 'uid,pid,t3ver_oid');
1501 return is_array($versionRec) ? $versionRec : $reqRecord;
1502 }
1503 } else {
1504 // This means that editing cannot occur on this record because it was not supporting versioning which is required inside an offline workspace.
1505 return FALSE;
1506 }
1507 } else {
1508 // In ONLINE workspace, just return the originally requested record:
1509 return $reqRecord;
1510 }
1511 } else {
1512 // Return FALSE because the table/uid was not found anyway.
1513 return FALSE;
1514 }
1515 }
1516
1517 /**
1518 * 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
1519 *
1520 * @return void
1521 * @deprecated since TYPO3 CMS 7, will be removed with TYPO3 CMS 8
1522 */
1523 public function editRegularContentFromId() {
1524 GeneralUtility::logDeprecatedFunction();
1525 $dbConnection = $this->getDatabaseConnection();
1526 $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');
1527 if ($dbConnection->sql_num_rows($res)) {
1528 $ecUids = array();
1529 while ($ecRec = $dbConnection->sql_fetch_assoc($res)) {
1530 $ecUids[] = $ecRec['uid'];
1531 }
1532 $this->editconf['tt_content'][implode(',', $ecUids)] = 'edit';
1533 }
1534 $dbConnection->sql_free_result($res);
1535 }
1536
1537 /**
1538 * Populates the variables $this->storeArray, $this->storeUrl, $this->storeUrlMd5
1539 *
1540 * @return void
1541 * @see makeDocSel()
1542 */
1543 public function compileStoreDat() {
1544 $this->storeArray = GeneralUtility::compileSelectedGetVarsFromArray('edit,defVals,overrideVals,columnsOnly,noView,editRegularContentFromId,workspace', $this->R_URL_getvars);
1545 $this->storeUrl = GeneralUtility::implodeArrayForUrl('', $this->storeArray);
1546 $this->storeUrlMd5 = md5($this->storeUrl);
1547 }
1548
1549 /**
1550 * Function used to look for configuration of buttons in the form: Fx. disabling buttons or showing them at various positions.
1551 *
1552 * @param string $table The table for which the configuration may be specific
1553 * @param string $key The option for look for. Default is checking if the saveDocNew button should be displayed.
1554 * @return string Return value fetched from USER TSconfig
1555 */
1556 public function getNewIconMode($table, $key = 'saveDocNew') {
1557 $TSconfig = $this->getBackendUser()->getTSConfig('options.' . $key);
1558 $output = trim(isset($TSconfig['properties'][$table]) ? $TSconfig['properties'][$table] : $TSconfig['value']);
1559 return $output;
1560 }
1561
1562 /**
1563 * Handling the closing of a document
1564 *
1565 * @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
1566 * @return void
1567 */
1568 public function closeDocument($code = 0) {
1569 // If current document is found in docHandler,
1570 // then unset it, possibly unset it ALL and finally, write it to the session data
1571 if (isset($this->docHandler[$this->storeUrlMd5])) {
1572 // add the closing document to the recent documents
1573 $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
1574 if (!is_array($recentDocs)) {
1575 $recentDocs = array();
1576 }
1577 $closedDoc = $this->docHandler[$this->storeUrlMd5];
1578 $recentDocs = array_merge(array($this->storeUrlMd5 => $closedDoc), $recentDocs);
1579 if (count($recentDocs) > 8) {
1580 $recentDocs = array_slice($recentDocs, 0, 8);
1581 }
1582 // remove it from the list of the open documents
1583 unset($this->docHandler[$this->storeUrlMd5]);
1584 if ($code == '3') {
1585 $recentDocs = array_merge($this->docHandler, $recentDocs);
1586 $this->docHandler = array();
1587 }
1588 $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
1589 $this->getBackendUser()->pushModuleData('FormEngine', array($this->docHandler, $this->docDat[1]));
1590 BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
1591 }
1592 // 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...)
1593 if ($this->returnEditConf && $this->retUrl != BackendUtility::getModuleUrl('dummy')) {
1594 $this->retUrl .= '&returnEditConf=' . rawurlencode(json_encode($this->editconf));
1595 }
1596 // If code is NOT set OR set to 1, then make a header location redirect to $this->retUrl
1597 if (!$code || $code == 1) {
1598 HttpUtility::redirect($this->retUrl);
1599 } else {
1600 $this->setDocument('', $this->retUrl);
1601 }
1602 }
1603
1604 /**
1605 * Redirects to the document pointed to by $currentDocFromHandlerMD5 OR $retUrl (depending on some internal calculations).
1606 * Most likely you will get a header-location redirect from this function.
1607 *
1608 * @param string $currentDocFromHandlerMD5 Pointer to the document in the docHandler array
1609 * @param string $retUrl Alternative/Default retUrl
1610 * @return void
1611 */
1612 public function setDocument($currentDocFromHandlerMD5 = '', $retUrl = '') {
1613 if ($retUrl === '') {
1614 return;
1615 }
1616 if (!$this->modTSconfig['properties']['disableDocSelector'] && is_array($this->docHandler) && !empty($this->docHandler)) {
1617 if (isset($this->docHandler[$currentDocFromHandlerMD5])) {
1618 $setupArr = $this->docHandler[$currentDocFromHandlerMD5];
1619 } else {
1620 $setupArr = reset($this->docHandler);
1621 }
1622 if ($setupArr[2]) {
1623 $sParts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
1624 $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
1625 }
1626 }
1627 HttpUtility::redirect($retUrl);
1628 }
1629
1630 /**
1631 * Injects the request object for the current request or subrequest
1632 *
1633 * @param ServerRequestInterface $request the current request
1634 * @param ResponseInterface $response
1635 * @return ResponseInterface the response with the content
1636 */
1637 public function mainAction(ServerRequestInterface $request, ResponseInterface $response) {
1638 BackendUtility::lockRecords();
1639
1640 // Preprocessing, storing data if submitted to
1641 $this->preInit();
1642
1643 // Checks, if a save button has been clicked (or the doSave variable is sent)
1644 if ($this->doProcessData()) {
1645 $this->processData();
1646 }
1647
1648 $this->init();
1649 $this->main();
1650
1651 $response->getBody()->write($this->content);
1652 return $response;
1653 }
1654
1655 /**
1656 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1657 */
1658 protected function getBackendUser() {
1659 return $GLOBALS['BE_USER'];
1660 }
1661
1662 /**
1663 * Returns LanguageService
1664 *
1665 * @return \TYPO3\CMS\Lang\LanguageService
1666 */
1667 protected function getLanguageService() {
1668 return $GLOBALS['LANG'];
1669 }
1670
1671 /**
1672 * Returns the database connection
1673 *
1674 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
1675 */
1676 protected function getDatabaseConnection() {
1677 return $GLOBALS['TYPO3_DB'];
1678 }
1679
1680 }