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