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