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