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