FrontendEditPanel.php 16.2 KB
Newer Older
1
<?php
2
namespace TYPO3\CMS\Feedit;
3

4
/**
5
6
7
8
9
10
11
12
13
14
15
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */
16

17
use TYPO3\CMS\Backend\Utility\BackendUtility;
Georg Ringer's avatar
Georg Ringer committed
18
use TYPO3\CMS\Backend\Utility\IconUtility;
19
20
use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
use TYPO3\CMS\Core\Database\DatabaseConnection;
21
use TYPO3\CMS\Core\Imaging\IconFactory;
22
use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
25
26
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Frontend\View\AdminPanelView;
27

28
29
30
31
32
33
/**
 * View class for the edit panels in frontend editing.
 */
class FrontendEditPanel {

	/**
34
35
	 * The Content Object Renderer
	 *
Georg Ringer's avatar
Georg Ringer committed
36
	 * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
37
38
39
	 */
	protected $cObj;

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
	/**
	 * Property for accessing TypoScriptFrontendController centrally
	 *
	 * @var TypoScriptFrontendController
	 */
	protected $frontendController;

	/**
	 * Property for accessing DatabaseConnection centrally
	 *
	 * @var DatabaseConnection
	 */
	protected $databaseConnection;

	/**
	 * @var FrontendBackendUserAuthentication
	 */
	protected $backendUser;

59
60
61
62
63
	/**
	 * @var \TYPO3\CMS\Core\Imaging\IconFactory
	 */
	protected $iconFactory;

64
	/**
Georg Ringer's avatar
Georg Ringer committed
65
	 * Constructor for the edit panel
66
67
68
69
	 *
	 * @param DatabaseConnection $databaseConnection
	 * @param TypoScriptFrontendController $frontendController
	 * @param FrontendBackendUserAuthentication $backendUser
70
	 */
71
72
73
74
	public function __construct(DatabaseConnection $databaseConnection = NULL, TypoScriptFrontendController $frontendController = NULL, FrontendBackendUserAuthentication $backendUser = NULL) {
		$this->databaseConnection = $databaseConnection ?: $GLOBALS['TYPO3_DB'];
		$this->frontendController = $frontendController ?: $GLOBALS['TSFE'];
		$this->backendUser = $backendUser ?: $GLOBALS['BE_USER'];
75
		$this->cObj = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
76
		$this->cObj->start(array());
77
		$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
78
79
80
81
82
83
84
85
86
87
88
89
	}

	/**
	 * 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.
	 * With the "edit panel" the user will see buttons with links to editing, moving, hiding, deleting the element
	 * This function is used for the cObject EDITPANEL and the stdWrap property ".editPanel"
	 *
	 * @param string $content 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.
	 * @param array $conf TypoScript configuration properties for the editPanel
	 * @param string $currentRecord 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
	 * @param array $dataArr Alternative data array to use. Default is $this->data
	 * @param string $table
90
	 * @param array $allow
91
	 * @param int $newUID
92
93
94
	 * @param array $hiddenFields
	 * @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.
	 */
95
	public function editPanel($content, array $conf, $currentRecord = '', array $dataArr = array(), $table = '', array $allow = array(), $newUID = 0, array $hiddenFields = array()) {
Georg Ringer's avatar
Georg Ringer committed
96
97
		$hiddenFieldString = $command = '';

98
		// Special content is about to be shown, so the cache must be disabled.
99
		$this->frontendController->set_no_cache('Frontend edit panel is shown', TRUE);
Georg Ringer's avatar
Georg Ringer committed
100

101
		$formName = 'TSFE_EDIT_FORM_' . substr($this->frontendController->uniqueHash(), 0, 4);
102
		$formTag = '<form name="' . $formName . '" id ="' . $formName . '" action="' . htmlspecialchars(GeneralUtility::getIndpEnv('REQUEST_URI')) . '" method="post" enctype="multipart/form-data" onsubmit="return TBE_EDITOR.checkSubmit(1);">';
103
104
105
		$sortField = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
		$labelField = $GLOBALS['TCA'][$table]['ctrl']['label'];
		$hideField = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
Georg Ringer's avatar
Georg Ringer committed
106
107

		$panel = '';
108
109
		if (isset($allow['toolbar']) && $this->backendUser->adminPanel instanceof AdminPanelView) {
			$panel .= $this->backendUser->adminPanel->ext_makeToolBar();
Georg Ringer's avatar
Georg Ringer committed
110
111
		}
		if (isset($allow['edit'])) {
112
			$icon = IconUtility::getSpriteIcon('actions-document-open', array('title' => $this->backendUser->extGetLL('p_editRecord')));
Georg Ringer's avatar
Georg Ringer committed
113
114
115
			$panel .= $this->editPanelLinkWrap($icon, $formName, 'edit', $dataArr['_LOCALIZED_UID'] ? $table . ':' . $dataArr['_LOCALIZED_UID'] : $currentRecord);
		}
		// Hiding in workspaces because implementation is incomplete
116
117
		if (isset($allow['move']) && $sortField && $this->backendUser->workspace === 0) {
			$icon = IconUtility::getSpriteIcon('actions-move-up', array('title' => $this->backendUser->extGetLL('p_moveUp')));
Georg Ringer's avatar
Georg Ringer committed
118
			$panel .= $this->editPanelLinkWrap($icon, $formName, 'up');
119
			$icon = IconUtility::getSpriteIcon('actions-move-down', array('title' => $this->backendUser->extGetLL('p_moveDown')));
Georg Ringer's avatar
Georg Ringer committed
120
121
122
123
			$panel .= $this->editPanelLinkWrap($icon, $formName, 'down');
		}
		// Hiding in workspaces because implementation is incomplete
		// Hiding for localizations because it is unknown what should be the function in that case
124
		if (isset($allow['hide']) && $hideField && $this->backendUser->workspace === 0 && !$dataArr['_LOCALIZED_UID']) {
Georg Ringer's avatar
Georg Ringer committed
125
			if ($dataArr[$hideField]) {
126
				$icon = $this->iconFactory->getIcon('actions-edit-unhide');
Georg Ringer's avatar
Georg Ringer committed
127
128
				$panel .= $this->editPanelLinkWrap($icon, $formName, 'unhide');
			} else {
129
				$icon = $this->iconFactory->getIcon('actions-edit-hide');
130
				$panel .= $this->editPanelLinkWrap($icon, $formName, 'hide', '', $this->backendUser->extGetLL('p_hideConfirm'));
Georg Ringer's avatar
Georg Ringer committed
131
132
133
134
			}
		}
		if (isset($allow['new'])) {
			if ($table === 'pages') {
135
				$icon = IconUtility::getSpriteIcon('actions-page-new', array('title' => $this->backendUser->extGetLL('p_newSubpage')));
Georg Ringer's avatar
Georg Ringer committed
136
137
				$panel .= $this->editPanelLinkWrap($icon, $formName, 'new', $currentRecord, '');
			} else {
138
				$icon = IconUtility::getSpriteIcon('actions-document-new', array('title' => $this->backendUser->extGetLL('p_newRecordAfter')));
Georg Ringer's avatar
Georg Ringer committed
139
140
141
142
143
				$panel .= $this->editPanelLinkWrap($icon, $formName, 'new', $currentRecord, '', $newUID);
			}
		}
		// Hiding in workspaces because implementation is incomplete
		// Hiding for localizations because it is unknown what should be the function in that case
144
145
146
		if (isset($allow['delete']) && $this->backendUser->workspace === 0 && !$dataArr['_LOCALIZED_UID']) {
			$icon = IconUtility::getSpriteIcon('actions-edit-delete', array('title' => $this->backendUser->extGetLL('p_delete')));
			$panel .= $this->editPanelLinkWrap($icon, $formName, 'delete', '', $this->backendUser->extGetLL('p_deleteConfirm'));
Georg Ringer's avatar
Georg Ringer committed
147
148
149
150
151
152
		}
		// Final
		$labelTxt = $this->cObj->stdWrap($conf['label'], $conf['label.']);
		foreach ((array)$hiddenFields as $name => $value) {
			$hiddenFieldString .= '<input type="hidden" name="TSFE_EDIT[' . htmlspecialchars($name) . ']" value="' . htmlspecialchars($value) . '"/>' . LF;
		}
153

Georg Ringer's avatar
Georg Ringer committed
154
		$panel = '<!-- BE_USER Edit Panel: -->
Wouter Wolters's avatar
Wouter Wolters committed
155
156
157
								' . $formTag . $hiddenFieldString . '
									<input type="hidden" name="TSFE_EDIT[cmd]" value="" />
									<input type="hidden" name="TSFE_EDIT[record]" value="' . $currentRecord . '" />
Georg Ringer's avatar
Georg Ringer committed
158
159
160
161
									<div class="typo3-editPanel">'
										. $panel .
			($labelTxt ? '<div class="typo3-editPanel-label">' . sprintf($labelTxt, htmlspecialchars(GeneralUtility::fixed_lgd_cs($dataArr[$labelField], 50))) . '</div>' : '') . '
									</div>
Wouter Wolters's avatar
Wouter Wolters committed
162
								</form>';
Georg Ringer's avatar
Georg Ringer committed
163
164
165
166

		// Wrap the panel
		if ($conf['innerWrap']) {
			$panel = $this->cObj->wrap($panel, $conf['innerWrap']);
167
		}
Georg Ringer's avatar
Georg Ringer committed
168
169
170
171
172
173
174
		if ($conf['innerWrap.']) {
			$panel = $this->cObj->stdWrap($panel, $conf['innerWrap.']);
		}

		// Wrap the complete panel
		if ($conf['outerWrap']) {
			$panel = $this->cObj->wrap($panel, $conf['outerWrap']);
175
		}
Georg Ringer's avatar
Georg Ringer committed
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
		if ($conf['outerWrap.']) {
			$panel = $this->cObj->stdWrap($panel, $conf['outerWrap.']);
		}
		if ($conf['printBeforeContent']) {
			$finalOut = $panel . $content;
		} else {
			$finalOut = $content . $panel;
		}

		$hidden = $this->isDisabled($table, $dataArr) ? ' typo3-feedit-element-hidden' : '';
		$outerWrapConfig = isset($conf['stdWrap.'])
			? $conf['stdWrap.']
			: array('wrap' => '<div class="typo3-feedit-element' . $hidden . '">|</div>');
		$finalOut = $this->cObj->stdWrap($finalOut, $outerWrapConfig);

191
192
193
194
		return $finalOut;
	}

	/**
195
	 * Adds an edit icon to the content string. The edit icon links to EditDocumentController with proper parameters for editing the table/fields of the context.
196
197
198
	 * This implements TYPO3 context sensitive editing facilities. Only backend users will have access (if properly configured as well).
	 *
	 * @param string $content The content to which the edit icons should be appended
199
	 * @param string $params 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 EditDocumentController
200
201
202
	 * @param array $conf TypoScript properties for configuring the edit icons.
	 * @param string $currentRecord 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
	 * @param array $dataArr Alternative data array to use. Default is $this->data
203
	 * @param string $addUrlParamStr Additional URL parameters for the link pointing to EditDocumentController
204
	 * @param string $table
205
	 * @param int $editUid
206
207
208
209
	 * @param string $fieldList
	 * @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.
	 */
	public function editIcons($content, $params, array $conf = array(), $currentRecord = '', array $dataArr = array(), $addUrlParamStr = '', $table, $editUid, $fieldList) {
Wouter Wolters's avatar
Wouter Wolters committed
210
		// Special content is about to be shown, so the cache must be disabled.
211
		$this->frontendController->set_no_cache('Display frontend edit icons', TRUE);
212
		$iconTitle = $this->cObj->stdWrap($conf['iconTitle'], $conf['iconTitle.']);
213
214
215
216
217
218
		$optionsArray = array(
			'title' => htmlspecialchars($iconTitle, ENT_COMPAT, 'UTF-8', FALSE),
			'class' => 'frontEndEditIcons',
			'style' => $conf['styleAttribute'] ? htmlspecialchars($conf['styleAttribute']) : ''
		);
		$iconImg = $conf['iconImg'] ? $conf['iconImg'] : IconUtility::getSpriteIcon('actions-document-open', $optionsArray);
219
		$nV = GeneralUtility::_GP('ADMCMD_view') ? 1 : 0;
220
221
222
223
224
225
226
227
228
229

		$url = BackendUtility::getModuleUrl(
			'record_edit',
			array(
				'edit[' . $table . '][' . $editUid . ']' => 'edit',
				'columnsOnly' => $fieldList,
				'noView' => $nV
			)
		) . $addUrlParamStr;
		$icon = $this->editPanelLinkWrap_doWrap($iconImg, $url);
230
231
232
233
		if ($conf['beforeLastTag'] < 0) {
			$content = $icon . $content;
		} elseif ($conf['beforeLastTag'] > 0) {
			$cBuf = rtrim($content);
Georg Ringer's avatar
Georg Ringer committed
234
235
			$secureCount = 30;
			while ($secureCount && substr($cBuf, -1) == '>' && substr($cBuf, -4) != '</a>') {
236
				$cBuf = rtrim(preg_replace('/<[^<]*>$/', '', $cBuf));
Georg Ringer's avatar
Georg Ringer committed
237
				$secureCount--;
238
			}
239
			$content = strlen($cBuf) && $secureCount ? substr($content, 0, strlen($cBuf)) . $icon . substr($content, strlen($cBuf)) : ($content = $icon . $content);
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
		} else {
			$content .= $icon;
		}
		return $content;
	}

	/**
	 * Helper function for editPanel() which wraps icons in the panel in a link with the action of the panel.
	 * The links are for some of them not simple hyperlinks but onclick-actions which submits a little form which the panel is wrapped in.
	 *
	 * @param string $string The string to wrap in a link, typ. and image used as button in the edit panel.
	 * @param string $formName The name of the form wrapping the edit panel.
	 * @param string $cmd The command of the link. There is a predefined list available: edit, new, up, down etc.
	 * @param string $currentRecord The "table:uid" of the record being processed by the panel.
	 * @param string $confirm Text string with confirmation message; If set a confirm box will be displayed before carrying out the action (if Yes is pressed)
Georg Ringer's avatar
Georg Ringer committed
255
	 * @param int|string $nPid "New pid" - for new records
256
257
258
	 * @return string A <a> tag wrapped string.
	 */
	protected function editPanelLinkWrap($string, $formName, $cmd, $currentRecord = '', $confirm = '', $nPid = '') {
259
		$nV = GeneralUtility::_GP('ADMCMD_view') ? 1 : 0;
Georg Ringer's avatar
Georg Ringer committed
260
		if ($cmd == 'edit') {
261
			$rParts = explode(':', $currentRecord);
262
			$out = $this->editPanelLinkWrap_doWrap($string, BackendUtility::getModuleUrl('record_edit', array('edit[' . $rParts[0] . '][' . $rParts[1] . ']' => 'edit', 'noView=' . $nV)), $currentRecord);
Georg Ringer's avatar
Georg Ringer committed
263
		} elseif ($cmd == 'new') {
264
265
			$rParts = explode(':', $currentRecord);
			if ($rParts[0] == 'pages') {
266
				$out = $this->editPanelLinkWrap_doWrap($string, BackendUtility::getModuleUrl('db_new', ['id' => $rParts[1], 'pagesOnly' => 1]), $currentRecord);
267
			} else {
268
				if (!(int)$nPid) {
269
					$nPid = MathUtility::canBeInterpretedAsInteger($rParts[1]) ? -$rParts[1] : $this->frontendController->id;
270
				}
271
				$out = $this->editPanelLinkWrap_doWrap($string, BackendUtility::getModuleUrl('record_edit', array('edit[' . $rParts[0] . '][' . $nPid . ']' => 'new', 'noView' => $nV)), $currentRecord);
272
273
			}
		} else {
274
			if ($confirm && $this->backendUser->jsConfirmation(JsConfirmation::FE_EDIT)) {
275
				// Gets htmlspecialchared later
276
				$cf1 = 'if (confirm(' . GeneralUtility::quoteJSvalue($confirm, TRUE) . ')) {';
277
278
279
280
				$cf2 = '}';
			} else {
				$cf1 = ($cf2 = '');
			}
281
			$out = '<a href="#" onclick="' . htmlspecialchars(($cf1 . 'document.' . $formName . '[\'TSFE_EDIT[cmd]\'].value=\'' . $cmd . '\'; document.' . $formName . '.submit();' . $cf2 . ' return false;')) . '">' . $string . '</a>';
282
283
284
285
286
		}
		return $out;
	}

	/**
287
	 * Creates a link to a script (eg. EditDocumentController or NewRecordController) which either opens in the current frame OR in a pop-up window.
288
289
290
291
	 *
	 * @param string $string The string to wrap in a link, typ. and image used as button in the edit panel.
	 * @param string $url The URL of the link. Should be absolute if supposed to work with <base> path set.
	 * @return string A <a> tag wrapped string.
292
	 * @see editPanelLinkWrap()
293
	 */
Georg Ringer's avatar
Georg Ringer committed
294
	protected function editPanelLinkWrap_doWrap($string, $url) {
295
		$onclick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($url . '&returnUrl=sysext/backend/Resources/Private/Templates/Close.html') . ',\'FEquickEditWindow\',\'width=690,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
296
		return '<a href="#" onclick="' . htmlspecialchars($onclick) . '" class="frontEndEditIconLinks">' . $string . '</a>';
297
298
299
	}

	/**
Georg Ringer's avatar
Georg Ringer committed
300
	 * Returns TRUE if the input table/row would be hidden in the frontend, according to the current time and simulate user group
301
302
303
	 *
	 * @param string $table The table name
	 * @param array $row The data record
304
	 * @return bool
305
	 */
Georg Ringer's avatar
Georg Ringer committed
306
307
	protected function isDisabled($table, array $row) {
		$status = FALSE;
Wouter Wolters's avatar
Wouter Wolters committed
308
309
310
311
		if (
			$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'] &&
			$row[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']] ||
			$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'] &&
312
313
			$this->frontendController->simUserGroup &&
			$row[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group']] == $this->frontendController->simUserGroup ||
Wouter Wolters's avatar
Wouter Wolters committed
314
315
316
317
318
319
			$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'] &&
			$row[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime']] > $GLOBALS['EXEC_TIME'] ||
			$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'] &&
			$row[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime']] &&
			$row[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime']] < $GLOBALS['EXEC_TIME']
		) {
Georg Ringer's avatar
Georg Ringer committed
320
			$status = TRUE;
321
322
		}

Georg Ringer's avatar
Georg Ringer committed
323
		return $status;
324
325
	}

326
}