Fixed bug #12017: Wrong Inclusion in tceforms and tceforms_fe
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_frontendedit.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008-2009 Jeff Segars <jeff@webempoweredchurch.org>
6 * (c) 2008-2009 David Slayback <dave@webempoweredchurch.org>
7 * All rights reserved
8 *
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 * A copy is found in the textfile GPL.txt and important notices to the license
18 * from the author is found in LICENSE.txt distributed with these scripts.
19 *
20 *
21 * This script is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * This copyright notice MUST APPEAR in all copies of the script!
27 ***************************************************************/
28 /**
29 * Controller class for frontend editing.
30 *
31 * $Id$
32 *
33 * @author Jeff Segars <jeff@webempoweredchurch.org>
34 * @author David Slayback <dave@webempoweredchurch.org>
35 * @package TYPO3
36 * @subpackage t3lib
37 */
38 class t3lib_frontendedit {
39
40 /**
41 * TCEmain object.
42 *
43 * @var t3lib_tcemain
44 */
45 protected $tce;
46
47 /**
48 * Initializes configuration options.
49 *
50 * @return void
51 */
52 public function initConfigOptions() {
53 $this->TSFE_EDIT = t3lib_div::_GP('TSFE_EDIT');
54
55 // Include classes for editing IF editing module in Admin Panel is open
56 if ($GLOBALS['BE_USER']->isFrontendEditingActive()) {
57 $GLOBALS['TSFE']->includeTCA();
58 if ($this->isEditAction()) {
59 $this->editAction();
60 }
61 }
62 }
63
64 /**
65 * Generates the "edit panels" which can be shown for a page or records on a page when the Admin Panel is enabled for a backend users surfing the frontend.
66 * With the "edit panel" the user will see buttons with links to editing, moving, hiding, deleting the element
67 * This function is used for the cObject EDITPANEL and the stdWrap property ".editPanel"
68 *
69 * @param string A content string containing the content related to the edit panel. For cObject "EDITPANEL" this is empty but not so for the stdWrap property. The edit panel is appended to this string and returned.
70 * @param array TypoScript configuration properties for the editPanel
71 * @param string The "table:uid" of the record being shown. If empty string then $this->currentRecord is used. For new records (set by $conf['newRecordFromTable']) it's auto-generated to "[tablename]:NEW"
72 * @param array Alternative data array to use. Default is $this->data
73 * @return string The input content string with the editPanel appended. This function returns only an edit panel appended to the content string if a backend user is logged in (and has the correct permissions). Otherwise the content string is directly returned.
74 * @link http://typo3.org/doc.0.html?&tx_extrepmgm_pi1[extUid]=270&tx_extrepmgm_pi1[tocEl]=375&cHash=7d8915d508
75 */
76 public function displayEditPanel($content, array $conf, $currentRecord, array $dataArray) {
77 if ($conf['newRecordFromTable']) {
78 $currentRecord = $conf['newRecordFromTable'] . ':NEW';
79 $conf['allow'] = 'new';
80 }
81
82 list($table, $uid) = explode(':', $currentRecord);
83
84 // Page ID for new records, 0 if not specified
85 $newRecordPid = intval($conf['newRecordInPid']);
86 if (!$conf['onlyCurrentPid'] || $dataArray['pid'] == $GLOBALS['TSFE']->id) {
87 if ($table=='pages') {
88 $newUid = $uid;
89 } else {
90 if ($conf['newRecordFromTable']) {
91 $newUid = $GLOBALS['TSFE']->id;
92 if ($newRecordPid) {
93 $newUid = $newRecordPid;
94 }
95 } else {
96 $newUid = -1 * $uid;
97 }
98 }
99 }
100
101 if ($GLOBALS['TSFE']->displayEditIcons && $table && $this->allowedToEdit($table, $dataArray, $conf) && $this->allowedToEditLanguage($table, $dataArray)) {
102 $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
103 if ($editClass) {
104 $edit = t3lib_div::getUserObj($editClass, false);
105 if (is_object($edit)) {
106 $allowedActions = $this->getAllowedEditActions($table, $conf, $dataArray['pid']);
107 $content = $edit->editPanel($content, $conf, $currentRecord, $dataArray, $table, $allowedActions, $newUid, $this->getHiddenFields($dataArray));
108 }
109 }
110 }
111
112 return $content;
113 }
114
115 /**
116 * Adds an edit icon to the content string. The edit icon links to alt_doc.php with proper parameters for editing the table/fields of the context.
117 * This implements TYPO3 context sensitive editing facilities. Only backend users will have access (if properly configured as well).
118 *
119 * @param string The content to which the edit icons should be appended
120 * @param string The parameters defining which table and fields to edit. Syntax is [tablename]:[fieldname],[fieldname],[fieldname],... OR [fieldname],[fieldname],[fieldname],... (basically "[tablename]:" is optional, default table is the one of the "current record" used in the function). The fieldlist is sent as "&columnsOnly=" parameter to alt_doc.php
121 * @param array TypoScript properties for configuring the edit icons.
122 * @param string The "table:uid" of the record being shown. If empty string then $this->currentRecord is used. For new records (set by $conf['newRecordFromTable']) it's auto-generated to "[tablename]:NEW"
123 * @param array Alternative data array to use. Default is $this->data
124 * @param string Additional URL parameters for the link pointing to alt_doc.php
125 * @return string The input content string, possibly with edit icons added (not necessarily in the end but just after the last string of normal content.
126 */
127
128 public function displayEditIcons($content, $params, array $conf=array(), $currentRecord = '', array $dataArray = array(), $addUrlParamStr = '') {
129 // Check incoming params:
130 list($currentRecordTable, $currentRecordUID) = explode(':', $currentRecord);
131 list($fieldList, $table) = array_reverse(t3lib_div::trimExplode(':', $params, 1)); // Reverse the array because table is optional
132 if (!$table) {
133 $table = $currentRecordTable;
134 } elseif ($table != $currentRecordTable) {
135 return $content; // If the table is set as the first parameter, and does not match the table of the current record, then just return.
136 }
137
138 $editUid = $dataArray['_LOCALIZED_UID'] ? $dataArray['_LOCALIZED_UID'] : $currentRecordUID;
139
140 // Edit icons imply that the editing action is generally allowed, assuming page and content element permissions permit it.
141 if (!array_key_exists('allow', $conf)) {
142 $conf['allow'] = 'edit';
143 }
144
145 if ($GLOBALS['TSFE']->displayFieldEditIcons && $table && $this->allowedToEdit($table, $dataArray, $conf) && $fieldList && $this->allowedToEditLanguage($table, $dataArray)) {
146 $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
147 if ($editClass) {
148 $edit = t3lib_div::getUserObj($editClass);
149 if (is_object($edit)) {
150 $content = $edit->editIcons($content, $params, $conf, $currentRecord, $dataArray, $addURLParamStr, $table, $editUid, $fieldList);
151 }
152 }
153 }
154
155 return $content;
156 }
157
158 /*****************************************************
159 *
160 * Frontend Editing
161 *
162 ****************************************************/
163
164 /**
165 * Returns true if an edit-action is sent from the Admin Panel
166 *
167 * @return boolean
168 * @see index_ts.php
169 */
170 public function isEditAction() {
171 if (is_array($this->TSFE_EDIT)) {
172 if ($this->TSFE_EDIT['cancel']) {
173 unset($this->TSFE_EDIT['cmd']);
174 } else {
175 $cmd = (string) $this->TSFE_EDIT['cmd'];
176 if (($cmd != 'edit' || (is_array($this->TSFE_EDIT['data']) && ($this->TSFE_EDIT['doSave'] || $this->TSFE_EDIT['update'] || $this->TSFE_EDIT['update_close']))) && $cmd != 'new') {
177 // $cmd can be a command like "hide" or "move". If $cmd is "edit" or "new" it's an indication to show the formfields. But if data is sent with update-flag then $cmd = edit is accepted because edit may be sent because of .keepGoing flag.
178 return true;
179 }
180 }
181 }
182 return false;
183 }
184
185 /**
186 * Returns true if an edit form is shown on the page.
187 * Used from index_ts.php where a true return-value will result in classes etc. being included.
188 *
189 * @return boolean
190 * @see index_ts.php
191 */
192 public function isEditFormShown() {
193 if (is_array($this->TSFE_EDIT)) {
194 $cmd = (string) $this->TSFE_EDIT['cmd'];
195 if ($cmd == 'edit' || $cmd == 'new') {
196 return true;
197 }
198 }
199 }
200
201 /**
202 * Management of the on-page frontend editing forms and edit panels.
203 * Basically taking in the data and commands and passes them on to the proper classes as they should be.
204 *
205 * @return void
206 * @throws UnexpectedValueException if TSFE_EDIT[cmd] is not a valid command
207 * @see index_ts.php
208 */
209 public function editAction() {
210 // Commands:
211 list($table, $uid) = explode(':', $this->TSFE_EDIT['record']);
212 $cmd = $this->TSFE_EDIT['cmd'];
213
214 // Look for some TSFE_EDIT data that indicates we should save.
215 if (($this->TSFE_EDIT['doSave'] || $this->TSFE_EDIT['update'] || $this->TSFE_EDIT['update_close']) && is_array($this->TSFE_EDIT['data'])) {
216 $cmd = 'save';
217 }
218
219 if (($cmd == 'save') || ($cmd && $table && $uid && isset($GLOBALS['TCA'][$table]))) {
220 // Hook for defining custom editing actions. Naming is incorrect, but preserves compatibility.
221 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['extEditAction'])) {
222 $_params = array();
223 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['extEditAction'] as $_funcRef) {
224 t3lib_div::callUserFunction($_funcRef, $_params, $this);
225 }
226 }
227
228 // Perform the requested editing command.
229 $cmdAction = 'do' . ucwords($cmd);
230 if (is_callable(array($this, $cmdAction))) {
231 $this->$cmdAction($table, $uid);
232 } else {
233 throw new UnexpectedValueException(
234 'The specified frontend edit command (' . $cmd . ') is not valid.',
235 1225818120
236 );
237 }
238 }
239 }
240
241 /**
242 * Hides a specific record.
243 *
244 * @param string The table name for the record to hide.
245 * @param integer The UID for the record to hide.
246 * @return void
247 */
248 public function doHide($table, $uid) {
249 $hideField = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
250 if ($hideField) {
251 $recData = array();
252 $recData[$table][$uid][$hideField] = 1;
253
254 $this->initializeTceMain();
255 $this->tce->start($recData, array());
256 $this->tce->process_datamap();
257 }
258 }
259
260 /**
261 * Unhides (shows) a specific record.
262 *
263 * @param string The table name for the record to unhide.
264 * @param integer The UID for the record to unhide.
265 * @return void
266 */
267 public function doUnhide($table, $uid) {
268 $hideField = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
269 if ($hideField) {
270 $recData = array();
271 $recData[$table][$uid][$hideField] = 0;
272
273 $this->initializeTceMain();
274 $this->tce->start($recData, array());
275 $this->tce->process_datamap();
276 }
277 }
278
279 /**
280 * Moves a record up.
281 *
282 * @param string The table name for the record to move.
283 * @param integer The UID for the record to hide.
284 * @return void
285 */
286 public function doUp($table, $uid) {
287 $this->move($table, $uid, 'up');
288 }
289
290 /**
291 * Moves a record down.
292 *
293 * @param string The table name for the record to move.
294 * @param integer The UID for the record to move.
295 * @return void
296 */
297 public function doDown($table, $uid) {
298 $this->move($table, $uid, 'down');
299 }
300
301 /**
302 * Moves a record after a given element. Used for drag.
303 *
304 * @param string The table name for the record to move.
305 * @param integer The UID for the record to move.
306 * @return void
307 */
308 public function doMoveAfter($table, $uid) {
309 $afterUID = $GLOBALS['BE_USER']->frontendEdit->TSFE_EDIT['moveAfter'];
310 $this->move($table, $uid, '', $afterUID);
311 }
312
313 /**
314 * Moves a record
315 *
316 * @param string The table name for the record to move.
317 * @param integer The UID for the record to move.
318 * @param string The direction to move, either 'up' or 'down'.
319 * @param integer The UID of record to move after. This is specified for dragging only.
320 * @return void
321 */
322 protected function move($table, $uid, $direction='', $afterUID=0) {
323 $cmdData = array();
324 $sortField = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
325 if ($sortField) {
326 // Get self:
327 $fields = array_unique(t3lib_div::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['copyAfterDuplFields'] . ',uid,pid,' . $sortField, true));
328 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(implode(',', $fields), $table, 'uid=' . $uid);
329 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
330 // record before or after
331 if (($GLOBALS['BE_USER']->adminPanel instanceOf tslib_AdminPanel) && ($GLOBALS['BE_USER']->adminPanel->extGetFeAdminValue('preview'))) {
332 $ignore = array('starttime'=>1, 'endtime'=>1, 'disabled'=>1, 'fe_group'=>1);
333 }
334 $copyAfterFieldsQuery = '';
335 if ($GLOBALS['TCA'][$table]['ctrl']['copyAfterDuplFields']) {
336 $cAFields = t3lib_div::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['copyAfterDuplFields'], true);
337 foreach($cAFields as $fieldName) {
338 $copyAfterFieldsQuery .= ' AND ' . $fieldName . '="' . $row[$fieldName] . '"';
339 }
340 }
341 if (!empty($direction)) {
342 if ($direction == 'up') {
343 $operator = '<';
344 $order = 'DESC';
345 } else {
346 $operator = '>';
347 $order = 'ASC';
348 }
349 $sortCheck = ' AND ' . $sortField . $operator . intval($row[$sortField]);
350 }
351 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
352 'uid,pid',
353 $table,
354 'pid=' . intval($row['pid']) .
355 $sortCheck .
356 $copyAfterFieldsQuery .
357 $GLOBALS['TSFE']->sys_page->enableFields($table, '', $ignore),
358 '',
359 $sortField . ' ' . $order,
360 '2'
361 );
362 if ($row2 = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
363 if ($afterUID) {
364 $cmdData[$table][$uid]['move'] = -$afterUID;
365 }
366 elseif ($direction == 'down') {
367 $cmdData[$table][$uid]['move'] = -$row2['uid'];
368 }
369 elseif ($row3 = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) { // Must take the second record above...
370 $cmdData[$table][$uid]['move'] = -$row3['uid'];
371 }
372 else { // ... and if that does not exist, use pid
373 $cmdData[$table][$uid]['move'] = $row['pid'];
374 }
375 } elseif ($direction == 'up') {
376 $cmdData[$table][$uid]['move'] = $row['pid'];
377 }
378 }
379 if (!empty($cmdData)) {
380 $this->initializeTceMain();
381 $this->tce->start(array(), $cmdData);
382 $this->tce->process_cmdmap();
383 }
384 }
385 }
386
387 /**
388 * Deletes a specific record.
389 *
390 * @param string The table name for the record to delete.
391 * @param integer The UID for the record to delete.
392 * @return void
393 */
394 public function doDelete($table, $uid) {
395 $cmdData[$table][$uid]['delete'] = 1;
396 if (count($cmdData)) {
397 $this->initializeTceMain();
398 $this->tce->start(array(), $cmdData);
399 $this->tce->process_cmdmap();
400 }
401 }
402
403 /**
404 * Saves a record based on its data array.
405 *
406 * @param string The table name for the record to save.
407 * @param integer The UID for the record to save.
408 * @return void
409 */
410 public function doSave($table, $uid) {
411 $data = $this->TSFE_EDIT['data'];
412
413 if (!empty($data)) {
414 $this->initializeTceMain();
415 $this->tce->start($data, array());
416 $this->tce->process_uploads($_FILES);
417 $this->tce->process_datamap();
418
419 // Save the new UID back into TSFE_EDIT
420 $newUID = $this->tce->substNEWwithIDs['NEW'];
421 if ($newUID) {
422 $GLOBALS['BE_USER']->frontendEdit->TSFE_EDIT['newUID'] = $newUID;
423 }
424 }
425 }
426
427 /**
428 * Saves a record based on its data array and closes it.
429 *
430 * @param string The table name for the record to save.
431 * @param integer The UID for the record to save.
432 * @return void
433 * @note This method is only a wrapper for doSave() but is needed so
434 * that frontend editing views can handle "save" differently from
435 * "save and close".
436 * Example: When editing a page record, "save" reloads the same
437 * editing form. "Save and close" reloads the entire page at
438 * the appropriate URL.
439 */
440 public function doSaveAndClose($table, $uid) {
441 $this->doSave($table, $uid);
442 }
443
444
445 /**
446 * Stub for closing a record. No real functionality needed since content
447 * element rendering will take care of everything.
448 *
449 * @param string The table name for the record to close.
450 * @param integer The UID for the record to close.
451 * @return void
452 */
453 public function doClose($table, $uid) {
454 // Do nothing.
455 }
456
457 /**
458 * Checks whether the user has access to edit the language for the
459 * requested record.
460 *
461 * @param string The name of the table.
462 * @param array The record.
463 * @return boolean
464 */
465 protected function allowedToEditLanguage($table, array $currentRecord) {
466 // If no access right to record languages, return immediately
467 if ($table === 'pages') {
468 $lang = $GLOBALS['TSFE']->sys_language_uid;
469 } elseif ($table === 'tt_content') {
470 $lang = $GLOBALS['TSFE']->sys_language_content;
471 } elseif ($GLOBALS['TCA'][$table]['ctrl']['languageField']) {
472 $lang = $currentRecord[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
473 } else {
474 $lang = -1;
475 }
476
477 if ($GLOBALS['BE_USER']->checkLanguageAccess($lang)) {
478 $languageAccess = true;
479 } else {
480 $languageAccess = false;
481 }
482
483 return $languageAccess;
484 }
485
486 /**
487 * Checks whether the user is allowed to edit the requested table.
488 *
489 * @param string The name of the table.
490 * @param array The data array.
491 * @param array The configuration array for the edit panel.
492 * @return boolean
493 */
494 protected function allowedToEdit($table, array $dataArray, array $conf) {
495
496 // Unless permissions specifically allow it, editing is not allowed.
497 $mayEdit = false;
498
499 if ($table=='pages') {
500 // 2 = permission to edit the page
501 if ($GLOBALS['BE_USER']->isAdmin() || $GLOBALS['BE_USER']->doesUserHaveAccess($dataArray, 2)) {
502 $mayEdit = true;
503 }
504 } else {
505 // 16 = permission to edit content on the page
506 if ($GLOBALS['BE_USER']->isAdmin() || $GLOBALS['BE_USER']->doesUserHaveAccess(t3lib_BEfunc::getRecord('pages', $dataArray['pid']), 16)) {
507 $mayEdit = true;
508 }
509 }
510
511 if (!$conf['onlyCurrentPid'] || ($dataArray['pid'] == $GLOBALS['TSFE']->id)) {
512 // Permissions:
513 $types = t3lib_div::trimExplode(',', t3lib_div::strtolower($conf['allow']),1);
514 $allow = array_flip($types);
515
516 $perms = $GLOBALS['BE_USER']->calcPerms($GLOBALS['TSFE']->page);
517 if ($table == 'pages') {
518 $allow = $this->getAllowedEditActions($table, $conf, $dataArray['pid'], $allow);
519
520 // Can only display editbox if there are options in the menu
521 if (count($allow)) {
522 $mayEdit = true;
523 }
524 } else {
525 $mayEdit = count($allow) && ($perms & 16);
526 }
527 }
528
529 return $mayEdit;
530 }
531
532 /**
533 * Takes an array of generally allowed actions and filters that list based on page and content permissions.
534 *
535 * @param string The name of the table.
536 * @param array The configuration array.
537 * @param integer The PID where editing will occur.
538 * @param string Comma-separated list of actions that are allowed in general.
539 * @return array
540 */
541 protected function getAllowedEditActions($table, array $conf, $pid, $allow = '') {
542
543 if (!$allow) {
544 $types = t3lib_div::trimExplode(',', t3lib_div::strtolower($conf['allow']), true);
545 $allow = array_flip($types);
546 }
547
548 if (!$conf['onlyCurrentPid'] || $pid == $GLOBALS['TSFE']->id) {
549 // Permissions:
550 $types = t3lib_div::trimExplode(',', t3lib_div::strtolower($conf['allow']), true);
551 $allow = array_flip($types);
552
553 $perms = $GLOBALS['BE_USER']->calcPerms($GLOBALS['TSFE']->page);
554 if ($table=='pages') {
555 // rootpage!
556 if (count($GLOBALS['TSFE']->config['rootLine']) == 1) {
557 unset($allow['move']);
558 unset($allow['hide']);
559 unset($allow['delete']);
560 }
561 if (!($perms & 2)){
562 unset($allow['edit']);
563 unset($allow['move']);
564 unset($allow['hide']);
565 }
566 if (!($perms & 4)) {
567 unset($allow['delete']);
568 }
569 if (!($perms&8)) {
570 unset($allow['new']);
571 }
572 }
573 }
574
575 return $allow;
576 }
577
578 /**
579 * Adds any extra Javascript includes needed for Front-end editing
580 *
581 * @param none
582 * @return string
583 */
584 public function getJavascriptIncludes() {
585 // No extra JS includes needed
586 return '';
587 }
588
589 /**
590 * Gets the hidden fields (array key=field name, value=field value) to be used in the edit panel for a particular content element.
591 * In the normal case, no hidden fields are needed but special controllers such as TemplaVoila need to track flexform pointers, etc.
592 *
593 * @param array The data array for a specific content element.
594 * @return array
595 */
596 public function getHiddenFields(array $dataArray) {
597 // No special hidden fields needed.
598 return array();
599 }
600
601 /**
602 * Initializes t3lib_TCEmain since it is used on modification actions.
603 *
604 * @return void
605 */
606 protected function initializeTceMain() {
607 if (!isset($this->tce)) {
608 $this->tce = t3lib_div::makeInstance('t3lib_TCEmain');
609 $this->tce->stripslashes_values=0;
610 }
611 }
612 }
613
614 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_frontendedit.php']) {
615 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_frontendedit.php']);
616 }
617
618 ?>