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