[!!!][FEATURE] Introduce PSR-7-based Routing for Backend AJAX Requests
[Packages/TYPO3.CMS.git] / typo3 / sysext / t3editor / Classes / T3editor.php
1 <?php
2 namespace TYPO3\CMS\T3editor;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Core\Page\PageRenderer;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Provides a javascript-driven code editor with syntax highlighting for TS, HTML, CSS and more
24 */
25 class T3editor implements \TYPO3\CMS\Core\SingletonInterface {
26
27 const MODE_TYPOSCRIPT = 'typoscript';
28 const MODE_JAVASCRIPT = 'javascript';
29 const MODE_CSS = 'css';
30 const MODE_XML = 'xml';
31 const MODE_HTML = 'html';
32 const MODE_PHP = 'php';
33 const MODE_SPARQL = 'sparql';
34 const MODE_MIXED = 'mixed';
35
36 /**
37 * @var string
38 */
39 protected $mode = '';
40
41 /**
42 * @var string
43 */
44 protected $ajaxSaveType = '';
45
46 /**
47 * Counts the editors on the current page
48 *
49 * @var int
50 */
51 protected $editorCounter = 0;
52
53 /**
54 * Relative path to EXT:t3editor
55 *
56 * @var string
57 */
58 protected $relExtPath = '';
59
60 /**
61 * Relative directory to codemirror
62 *
63 * @var string
64 */
65 protected $codemirrorPath = 'sysext/t3editor/Resources/Public/JavaScript/Contrib/codemirror/js/';
66
67 /**
68 * RequireJS modules loaded for code completion
69 *
70 * @var array
71 */
72 protected $codeCompletionComponents = array('TsRef', 'CompletionResult', 'TsParser', 'TsCodeCompletion');
73
74 /**
75 * sets the type of code to edit (::MODE_TYPOSCRIPT, ::MODE_JAVASCRIPT)
76 *
77 * @param $mode string Expects one of the predefined constants
78 * @return \TYPO3\CMS\T3editor\T3editor
79 */
80 public function setMode($mode) {
81 $this->mode = $mode;
82 return $this;
83 }
84
85 /**
86 * Set the AJAX save type
87 *
88 * @param string $ajaxSaveType
89 * @return \TYPO3\CMS\T3editor\T3editor
90 */
91 public function setAjaxSaveType($ajaxSaveType) {
92 $this->ajaxSaveType = $ajaxSaveType;
93 return $this;
94 }
95
96 /**
97 * Set mode by file
98 *
99 * @param string $file
100 * @return string
101 */
102 public function setModeByFile($file) {
103 $fileInfo = GeneralUtility::split_fileref($file);
104 // @TODO: @FIXME: the method setModeByType returns void, so this method will never return a string
105 return $this->setModeByType($fileInfo['fileext']);
106 }
107
108 /**
109 * Set mode by type
110 *
111 * @param string $type
112 * @return void
113 */
114 public function setModeByType($type) {
115 switch ($type) {
116 case 'html':
117 case 'htm':
118 case 'tmpl':
119 $mode = self::MODE_HTML;
120 break;
121 case 'js':
122 $mode = self::MODE_JAVASCRIPT;
123 break;
124 case 'xml':
125 case 'svg':
126 $mode = self::MODE_XML;
127 break;
128 case 'css':
129 $mode = self::MODE_CSS;
130 break;
131 case 'ts':
132 $mode = self::MODE_TYPOSCRIPT;
133 break;
134 case 'sparql':
135 $mode = self::MODE_SPARQL;
136 break;
137 case 'php':
138 case 'phpsh':
139 case 'inc':
140 $mode = self::MODE_PHP;
141 break;
142 default:
143 $mode = self::MODE_MIXED;
144 }
145 $this->setMode($mode);
146 }
147
148 /**
149 * Get mode
150 *
151 * @return string
152 */
153 public function getMode() {
154 return $this->mode;
155 }
156
157 /**
158 * @return bool TRUE if the t3editor is enabled
159 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8
160 */
161 public function isEnabled() {
162 GeneralUtility::logDeprecatedFunction();
163 return TRUE;
164 }
165
166 /**
167 * Creates a new instance of the class
168 */
169 public function __construct() {
170 $GLOBALS['LANG']->includeLLFile('EXT:t3editor/Resources/Private/Language/locallang.xlf');
171 // Disable pmktextarea to avoid conflicts (thanks Peter Klein for this suggestion)
172 $GLOBALS['BE_USER']->uc['disablePMKTextarea'] = 1;
173
174 $this->relExtPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('t3editor');
175 }
176
177 /**
178 * Retrieves JavaScript code (header part) for editor
179 *
180 * @param \TYPO3\CMS\Backend\Template\DocumentTemplate $doc
181 * @return string
182 */
183 public function getJavascriptCode($doc) {
184 /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
185 $pageRenderer = $this->getPageRenderer();
186
187 $doc->addStyleSheet('t3editor', $this->relExtPath . 'Resources/Public/Css/t3editor.css');
188
189 // Include editor-js-lib
190 $doc->loadJavascriptLib($this->codemirrorPath . 'codemirror.js');
191 $this->loadTypoScriptCodeCompletion($doc);
192 $pageRenderer->loadRequireJsModule('TYPO3/CMS/T3editor/T3editor');
193 return '';
194 }
195
196 /**
197 * Load additional code completion for TypoScript
198 *
199 * @param \TYPO3\CMS\Backend\Template\DocumentTemplate $doc
200 */
201 protected function loadTypoScriptCodeCompletion($doc) {
202 if ($this->mode === self::MODE_TYPOSCRIPT) {
203 $pageRenderer = $doc->getPageRenderer();
204 foreach ($this->codeCompletionComponents as $codeCompletionComponent) {
205 $pageRenderer->loadRequireJsModule('TYPO3/CMS/T3editor/Plugins/CodeCompletion/' . $codeCompletionComponent);
206 }
207 }
208 }
209
210 /**
211 * Get the template code, prepared for javascript (no line breaks, quoted in single quotes)
212 *
213 * @return string The template code, prepared to use in javascript
214 */
215 protected function getPreparedTemplate() {
216 $T3editor_template = GeneralUtility::getUrl(
217 GeneralUtility::getFileAbsFileName('EXT:t3editor/Resources/Private/Templates/t3editor.html')
218 );
219 return str_replace(array(CR, LF), '', $T3editor_template);
220 }
221
222 /**
223 * Determine the correct parser js file for given mode
224 *
225 * @param string $mode
226 * @return string Parser file name
227 */
228 protected function getParserfileByMode($mode) {
229 switch ($mode) {
230 case self::MODE_TYPOSCRIPT:
231 $relPath = '../../../parse_typoscript/';
232 $parserfile = array($relPath . 'tokenizetyposcript.js', $relPath . 'parsetyposcript.js');
233 break;
234 case self::MODE_JAVASCRIPT:
235 $parserfile = array('tokenizetyposcript.js', 'parsejavascript.js');
236 break;
237 case self::MODE_CSS:
238 $parserfile = array('parsecss.js');
239 break;
240 case self::MODE_XML:
241 $parserfile = array('parsexml.js');
242 break;
243 case self::MODE_SPARQL:
244 $parserfile = array('parsesparql.js');
245 break;
246 case self::MODE_HTML:
247 $parserfile = array('tokenizejavascript.js', 'parsejavascript.js', 'parsecss.js', 'parsexml.js', 'parsehtmlmixed.js');
248 break;
249 case self::MODE_PHP:
250 case self::MODE_MIXED:
251 $parserfile = array('tokenizejavascript.js', 'parsejavascript.js', 'parsecss.js', 'parsexml.js', '../contrib/php/js/tokenizephp.js', '../contrib/php/js/parsephp.js', '../contrib/php/js/parsephphtmlmixed.js');
252 break;
253 }
254 return json_encode($parserfile);
255 }
256
257 /**
258 * Determine the correct css file for given mode
259 *
260 * @param string $mode
261 * @return string css file name
262 */
263 protected function getStylesheetByMode($mode) {
264 switch ($mode) {
265 case self::MODE_TYPOSCRIPT:
266 $stylesheet = array($this->relExtPath . 'Resources/Public/Css/typoscriptcolors.css');
267 break;
268 case self::MODE_JAVASCRIPT:
269 $stylesheet = array($this->codemirrorPath . '../css/jscolors.css');
270 break;
271 case self::MODE_CSS:
272 $stylesheet = array($this->codemirrorPath . '../css/csscolors.css');
273 break;
274 case self::MODE_XML:
275 $stylesheet = array($this->codemirrorPath . '../css/xmlcolors.css');
276 break;
277 case self::MODE_HTML:
278 $stylesheet = array($this->codemirrorPath . '../css/xmlcolors.css', $this->codemirrorPath . '../css/jscolors.css', $this->codemirrorPath . '../css/csscolors.css');
279 break;
280 case self::MODE_SPARQL:
281 $stylesheet = array($this->codemirrorPath . '../css/sparqlcolors.css');
282 break;
283 case self::MODE_PHP:
284 $stylesheet = array($this->codemirrorPath . '../contrib/php/css/phpcolors.css');
285 break;
286 case self::MODE_MIXED:
287 $stylesheet = array($this->codemirrorPath . '../css/xmlcolors.css', $this->codemirrorPath . '../css/jscolors.css', $this->codemirrorPath . '../css/csscolors.css', $this->codemirrorPath . '../contrib/php/css/phpcolors.css');
288 break;
289 default:
290 $stylesheet = array();
291 }
292 $stylesheet[] = $this->relExtPath . 'Resources/Public/Css/t3editor_inner.css';
293 return json_encode($stylesheet);
294 }
295
296 /**
297 * Generates HTML with code editor
298 *
299 * @param string $name Name attribute of HTML tag
300 * @param string $class Class attribute of HTML tag
301 * @param string $content Content of the editor
302 * @param string $additionalParams Any additional editor parameters
303 * @param string $alt Alt attribute
304 * @param array $hiddenfields
305 * @return string Generated HTML code for editor
306 */
307 public function getCodeEditor($name, $class = '', $content = '', $additionalParams = '', $alt = '', array $hiddenfields = array()) {
308 $code = '';
309 $class .= ' t3editor';
310 $alt = htmlspecialchars($alt);
311 if (!empty($alt)) {
312 $alt = ' alt="' . $alt . '"';
313 }
314 $code .=
315 '<div class="t3editor">'
316 . '<div class="t3e_wrap">'
317 . $this->getPreparedTemplate()
318 . '</div>'
319 . '<textarea '
320 . 'id="t3editor_' . $this->editorCounter . '" '
321 . 'name="' . $name . '" '
322 . 'class="' . $class . '" '
323 . $additionalParams . ' '
324 . $alt
325 . ' data-labels="' . htmlspecialchars(json_encode($GLOBALS['LANG']->getLabelsWithPrefix('js.', 'label_'))) . '"'
326 . ' data-instance-number="' . $this->editorCounter . '"'
327 . ' data-editor-path="' . htmlspecialchars($this->relExtPath) . '"'
328 . ' data-codemirror-path="' . htmlspecialchars($this->codemirrorPath) . '"'
329 . ' data-ajaxsavetype="' . htmlspecialchars($this->ajaxSaveType) . '"'
330 . ' data-parserfile="' . htmlspecialchars($this->getParserfileByMode($this->mode)) . '"'
331 . ' data-stylesheet="' . htmlspecialchars($this->getStylesheetByMode($this->mode)) . '"'
332 . '>' . htmlspecialchars($content)
333 . '</textarea>'
334 . '</div>';
335 if (!empty($hiddenfields)) {
336 foreach ($hiddenfields as $name => $value) {
337 $code .= '<input type="hidden" ' . 'name="' . $name . '" ' . 'value="' . $value . '" />';
338 }
339 }
340 $this->editorCounter++;
341 return $code;
342 }
343
344 /**
345 * Save the content from t3editor retrieved via Ajax
346 *
347 * @param ServerRequestInterface $request
348 * @param ResponseInterface $response
349 * @return ResponseInterface
350 */
351 public function ajaxSaveCode(ServerRequestInterface $request, ResponseInterface $response) {
352 // cancel if its not an Ajax request
353 if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
354 $codeType = isset($request->getParsedBody()['t3editor_savetype']) ? $request->getParsedBody()['t3editor_savetype'] : $request->getQueryParams()['t3editor_savetype'];
355 $savingsuccess = FALSE;
356 try {
357 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/t3editor/classes/class.tx_t3editor.php']['ajaxSaveCode'])) {
358 $_params = array(
359 'pObj' => &$this,
360 'type' => $codeType,
361 'request' => $request,
362 'response' => $response
363 );
364 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/t3editor/classes/class.tx_t3editor.php']['ajaxSaveCode'] as $key => $_funcRef) {
365 $savingsuccess = GeneralUtility::callUserFunction($_funcRef, $_params, $this) || $savingsuccess;
366 }
367 }
368 $responseContent = array('result' => $savingsuccess);
369 } catch (\Exception $e) {
370 $responseContent = array(
371 'result' => FALSE,
372 'exceptionMessage' => htmlspecialchars($e->getMessage()),
373 'exceptionCode' => $e->getCode()
374 );
375 }
376 /** @var \TYPO3\CMS\Core\Http\Response $response */
377 $response = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\Response::class);
378 $response->getBody()->write(json_encode($responseContent));
379 }
380
381 return $response;
382 }
383
384 /**
385 * Gets plugins that are defined at $TYPO3_CONF_VARS['EXTCONF']['t3editor']['plugins']
386 * (called by typo3/ajax.php)
387 *
388 * @param ServerRequestInterface $request
389 * @param ResponseInterface $response
390 * @return ResponseInterface
391 */
392 public function getPlugins(ServerRequestInterface $request, ResponseInterface $response) {
393 $result = array();
394 $plugins = &$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3editor']['plugins'];
395 if (is_array($plugins)) {
396 $result = array_values($plugins);
397 }
398 $request->getBody()->write(json_encode($result));
399 return $request;
400 }
401
402 /**
403 * @return PageRenderer
404 */
405 protected function getPageRenderer() {
406 return GeneralUtility::makeInstance(PageRenderer::class);
407 }
408
409 }