[TASK] Decouple t3editor hooks from DocumentTemplate
[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\ExtensionManagementUtility;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
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 * Relative path to EXT:t3editor
56 *
57 * @var string
58 */
59 protected $relExtPath = '';
60
61 /**
62 * Relative directory to codemirror
63 *
64 * @var string
65 */
66 protected $codemirrorPath = 'sysext/t3editor/Resources/Public/JavaScript/Contrib/codemirror/js/';
67
68 /**
69 * RequireJS modules loaded for code completion
70 *
71 * @var array
72 */
73 protected $codeCompletionComponents = array('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 $this->mode = $mode;
83 return $this;
84 }
85
86 /**
87 * Set the AJAX save type
88 *
89 * @param string $ajaxSaveType
90 * @return \TYPO3\CMS\T3editor\T3editor
91 */
92 public function setAjaxSaveType($ajaxSaveType) {
93 $this->ajaxSaveType = $ajaxSaveType;
94 return $this;
95 }
96
97 /**
98 * Set mode by file
99 *
100 * @param string $file
101 * @return string
102 */
103 public function setModeByFile($file) {
104 $fileInfo = GeneralUtility::split_fileref($file);
105 // @TODO: @FIXME: the method setModeByType returns void, so this method will never return a string
106 return $this->setModeByType($fileInfo['fileext']);
107 }
108
109 /**
110 * Set mode by type
111 *
112 * @param string $type
113 * @return void
114 */
115 public function setModeByType($type) {
116 switch ($type) {
117 case 'html':
118 case 'htm':
119 case 'tmpl':
120 $mode = self::MODE_HTML;
121 break;
122 case 'js':
123 $mode = self::MODE_JAVASCRIPT;
124 break;
125 case 'xml':
126 case 'svg':
127 $mode = self::MODE_XML;
128 break;
129 case 'css':
130 $mode = self::MODE_CSS;
131 break;
132 case 'ts':
133 $mode = self::MODE_TYPOSCRIPT;
134 break;
135 case 'sparql':
136 $mode = self::MODE_SPARQL;
137 break;
138 case 'php':
139 case 'phpsh':
140 case 'inc':
141 $mode = self::MODE_PHP;
142 break;
143 default:
144 $mode = self::MODE_MIXED;
145 }
146 $this->setMode($mode);
147 }
148
149 /**
150 * Get mode
151 *
152 * @return string
153 */
154 public function getMode() {
155 return $this->mode;
156 }
157
158 /**
159 * @return bool TRUE if the t3editor is enabled
160 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8
161 */
162 public function isEnabled() {
163 GeneralUtility::logDeprecatedFunction();
164 return TRUE;
165 }
166
167 /**
168 * Creates a new instance of the class
169 */
170 public function __construct() {
171 $GLOBALS['LANG']->includeLLFile('EXT:t3editor/Resources/Private/Language/locallang.xlf');
172 // Disable pmktextarea to avoid conflicts (thanks Peter Klein for this suggestion)
173 $GLOBALS['BE_USER']->uc['disablePMKTextarea'] = 1;
174
175 $this->relExtPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('t3editor');
176 }
177
178 /**
179 * Retrieves JavaScript code (header part) for editor
180 *
181 * @return string
182 */
183 public function getJavascriptCode() {
184 /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
185 $pageRenderer = $this->getPageRenderer();
186 $pageRenderer->addCssFile($this->relExtPath . 'Resources/Public/Css/t3editor.css');
187 // Include editor-js-lib
188 $pageRenderer->addJsLibrary('codemirror', $this->codemirrorPath . 'codemirror.js');
189 if ($this->mode === self::MODE_TYPOSCRIPT) {
190 foreach ($this->codeCompletionComponents as $codeCompletionComponent) {
191 $pageRenderer->loadRequireJsModule('TYPO3/CMS/T3editor/Plugins/CodeCompletion/' . $codeCompletionComponent);
192 }
193 }
194 $pageRenderer->loadRequireJsModule('TYPO3/CMS/T3editor/T3editor');
195 return '';
196 }
197
198 /**
199 * Get the template code, prepared for javascript (no line breaks, quoted in single quotes)
200 *
201 * @return string The template code, prepared to use in javascript
202 */
203 protected function getPreparedTemplate() {
204 $T3editor_template = GeneralUtility::getUrl(
205 GeneralUtility::getFileAbsFileName('EXT:t3editor/Resources/Private/Templates/t3editor.html')
206 );
207 return str_replace(array(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 switch ($mode) {
218 case self::MODE_TYPOSCRIPT:
219 $relPath = '../../../parse_typoscript/';
220 $parserfile = array($relPath . 'tokenizetyposcript.js', $relPath . 'parsetyposcript.js');
221 break;
222 case self::MODE_JAVASCRIPT:
223 $parserfile = array('tokenizetyposcript.js', 'parsejavascript.js');
224 break;
225 case self::MODE_CSS:
226 $parserfile = array('parsecss.js');
227 break;
228 case self::MODE_XML:
229 $parserfile = array('parsexml.js');
230 break;
231 case self::MODE_SPARQL:
232 $parserfile = array('parsesparql.js');
233 break;
234 case self::MODE_HTML:
235 $parserfile = array('tokenizejavascript.js', 'parsejavascript.js', 'parsecss.js', 'parsexml.js', 'parsehtmlmixed.js');
236 break;
237 case self::MODE_PHP:
238 case self::MODE_MIXED:
239 $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');
240 break;
241 }
242 return json_encode($parserfile);
243 }
244
245 /**
246 * Determine the correct css file for given mode
247 *
248 * @param string $mode
249 * @return string css file name
250 */
251 protected function getStylesheetByMode($mode) {
252 switch ($mode) {
253 case self::MODE_TYPOSCRIPT:
254 $stylesheet = array($this->relExtPath . 'Resources/Public/Css/typoscriptcolors.css');
255 break;
256 case self::MODE_JAVASCRIPT:
257 $stylesheet = array($this->codemirrorPath . '../css/jscolors.css');
258 break;
259 case self::MODE_CSS:
260 $stylesheet = array($this->codemirrorPath . '../css/csscolors.css');
261 break;
262 case self::MODE_XML:
263 $stylesheet = array($this->codemirrorPath . '../css/xmlcolors.css');
264 break;
265 case self::MODE_HTML:
266 $stylesheet = array($this->codemirrorPath . '../css/xmlcolors.css', $this->codemirrorPath . '../css/jscolors.css', $this->codemirrorPath . '../css/csscolors.css');
267 break;
268 case self::MODE_SPARQL:
269 $stylesheet = array($this->codemirrorPath . '../css/sparqlcolors.css');
270 break;
271 case self::MODE_PHP:
272 $stylesheet = array($this->codemirrorPath . '../contrib/php/css/phpcolors.css');
273 break;
274 case self::MODE_MIXED:
275 $stylesheet = array($this->codemirrorPath . '../css/xmlcolors.css', $this->codemirrorPath . '../css/jscolors.css', $this->codemirrorPath . '../css/csscolors.css', $this->codemirrorPath . '../contrib/php/css/phpcolors.css');
276 break;
277 default:
278 $stylesheet = array();
279 }
280 $stylesheet[] = $this->relExtPath . 'Resources/Public/Css/t3editor_inner.css';
281 return json_encode($stylesheet);
282 }
283
284 /**
285 * Generates HTML with code editor
286 *
287 * @param string $name Name attribute of HTML tag
288 * @param string $class Class attribute of HTML tag
289 * @param string $content Content of the editor
290 * @param string $additionalParams Any additional editor parameters
291 * @param string $alt Alt attribute
292 * @param array $hiddenfields
293 * @return string Generated HTML code for editor
294 */
295 public function getCodeEditor($name, $class = '', $content = '', $additionalParams = '', $alt = '', array $hiddenfields = array()) {
296 $code = '';
297 $class .= ' t3editor';
298 $alt = htmlspecialchars($alt);
299 if (!empty($alt)) {
300 $alt = ' alt="' . $alt . '"';
301 }
302 $code .=
303 '<div class="t3editor">'
304 . '<div class="t3e_wrap">'
305 . $this->getPreparedTemplate()
306 . '</div>'
307 . '<textarea '
308 . 'id="t3editor_' . $this->editorCounter . '" '
309 . 'name="' . $name . '" '
310 . 'class="' . $class . '" '
311 . $additionalParams . ' '
312 . $alt
313 . ' data-labels="' . htmlspecialchars(json_encode($GLOBALS['LANG']->getLabelsWithPrefix('js.', 'label_'))) . '"'
314 . ' data-instance-number="' . $this->editorCounter . '"'
315 . ' data-editor-path="' . htmlspecialchars($this->relExtPath) . '"'
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="' . $name . '" ' . 'value="' . $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 // cancel if its not an Ajax request
341 if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
342 $codeType = isset($request->getParsedBody()['t3editor_savetype']) ? $request->getParsedBody()['t3editor_savetype'] : $request->getQueryParams()['t3editor_savetype'];
343 $savingsuccess = FALSE;
344 try {
345 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/t3editor/classes/class.tx_t3editor.php']['ajaxSaveCode'])) {
346 $_params = array(
347 'pObj' => &$this,
348 'type' => $codeType,
349 'request' => $request,
350 'response' => $response
351 );
352 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/t3editor/classes/class.tx_t3editor.php']['ajaxSaveCode'] as $key => $_funcRef) {
353 $savingsuccess = GeneralUtility::callUserFunction($_funcRef, $_params, $this) || $savingsuccess;
354 }
355 }
356 $responseContent = array('result' => $savingsuccess);
357 } catch (\Exception $e) {
358 $responseContent = array(
359 'result' => FALSE,
360 'exceptionMessage' => htmlspecialchars($e->getMessage()),
361 'exceptionCode' => $e->getCode()
362 );
363 }
364 /** @var \TYPO3\CMS\Core\Http\Response $response */
365 $response = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\Response::class);
366 $response->getBody()->write(json_encode($responseContent));
367 }
368
369 return $response;
370 }
371
372 /**
373 * Gets plugins that are defined at $TYPO3_CONF_VARS['EXTCONF']['t3editor']['plugins']
374 * (called by typo3/ajax.php)
375 *
376 * @param ServerRequestInterface $request
377 * @param ResponseInterface $response
378 * @return ResponseInterface
379 */
380 public function getPlugins(ServerRequestInterface $request, ResponseInterface $response) {
381 $result = array();
382 $plugins = &$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3editor']['plugins'];
383 if (is_array($plugins)) {
384 $result = array_values($plugins);
385 }
386 $request->getBody()->write(json_encode($result));
387 return $request;
388 }
389
390 /**
391 * @return PageRenderer
392 */
393 protected function getPageRenderer() {
394 return GeneralUtility::makeInstance(PageRenderer::class);
395 }
396
397 }