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