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