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