70bad5fbad253156df10cad884029688ffa36841
[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
181 $content .= \TYPO3\CMS\Core\Utility\GeneralUtility::wrapJS(
182 'T3editor = T3editor || {};' .
183 'T3editor.lang = ' . json_encode($this->getJavaScriptLabels()) . ';' . LF .
184 'T3editor.PATH_t3e = "' . $GLOBALS['BACK_PATH'] . $path_t3e . '"; ' . LF .
185 'T3editor.PATH_codemirror = "' . $GLOBALS['BACK_PATH'] . $path_codemirror . '"; ' . LF .
186 'T3editor.URL_typo3 = "' . htmlspecialchars(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir) . '"; ' . 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 * Gets the labels to be used in JavaScript in the Ext JS interface.
305 * TODO this method is copied from EXT:Recycler, maybe this should be refactored into a helper class
306 *
307 * @return array The labels to be used in JavaScript
308 */
309 protected function getJavaScriptLabels() {
310 $coreLabels = array();
311 $extensionLabels = $this->getJavaScriptLabelsFromLocallang('js.', 'label_');
312 return array_merge($coreLabels, $extensionLabels);
313 }
314
315 /**
316 * Gets labels to be used in JavaScript fetched from the current locallang file.
317 * TODO this method is copied from EXT:Recycler, maybe this should be refactored into a helper class
318 *
319 * @param string $selectionPrefix Prefix to select the correct labels (default: 'js.')
320 * @param string $stripFromSelectionName Sub-prefix to be removed from label names in the result (default: '')
321 * @return array Lables to be used in JavaScript of the current locallang file
322 * @todo Check, whether this method can be moved in a generic way to $GLOBALS['LANG']
323 */
324 protected function getJavaScriptLabelsFromLocallang($selectionPrefix = 'js.', $stripFromSelectionName = '') {
325 $extraction = array();
326 $labels = array_merge((array)$GLOBALS['LOCAL_LANG']['default'], (array)$GLOBALS['LOCAL_LANG'][$GLOBALS['LANG']->lang]);
327 // Regular expression to strip the selection prefix and possibly something from the label name:
328 $labelPattern = '#^' . preg_quote($selectionPrefix, '#') . '(' . preg_quote($stripFromSelectionName, '#') . ')?#';
329 // Iterate throuh all locallang lables:
330 foreach ($labels as $label => $value) {
331 if (strpos($label, $selectionPrefix) === 0) {
332 $key = preg_replace($labelPattern, '', $label);
333 $extraction[$key] = $value;
334 }
335 }
336 return $extraction;
337 }
338
339 /**
340 * Generates HTML with code editor
341 *
342 * @param string $name Name attribute of HTML tag
343 * @param string $class Class attribute of HTML tag
344 * @param string $content Content of the editor
345 * @param string $additionalParams Any additional editor parameters
346 * @param string $alt Alt attribute
347 * @param array $hiddenfields
348 * @return string Generated HTML code for editor
349 */
350 public function getCodeEditor($name, $class = '', $content = '', $additionalParams = '', $alt = '', array $hiddenfields = array()) {
351 $code = '';
352 if ($this->isEnabled()) {
353 $this->editorCounter++;
354 $class .= ' t3editor';
355 $alt = htmlspecialchars($alt);
356 if (!empty($alt)) {
357 $alt = ' alt="' . $alt . '"';
358 }
359 $code .= '<div>' . '<textarea id="t3editor_' . $this->editorCounter . '" ' . 'name="' . $name . '" ' . 'class="' . $class . '" ' . $additionalParams . ' ' . $alt . '>' . htmlspecialchars($content) . '</textarea></div>';
360 $checked = $GLOBALS['BE_USER']->uc['disableT3Editor'] ? 'checked="checked"' : '';
361 $code .= '<br /><br />' . '<input type="checkbox" ' . 'class="checkbox t3editor_disableEditor" ' . 'onclick="T3editor.toggleEditor(this);" ' . 'name="t3editor_disableEditor" ' . 'value="true" ' . 'id="t3editor_disableEditor_' . $this->editorCounter . '_checkbox" ' . $checked . ' />&nbsp;' . '<label for="t3editor_disableEditor_' . $this->editorCounter . '_checkbox">' . $GLOBALS['LANG']->getLL('deactivate') . '</label>' . '<br /><br />';
362 if (count($hiddenfields)) {
363 foreach ($hiddenfields as $name => $value) {
364 $code .= '<input type="hidden" ' . 'name="' . $name . '" ' . 'value="' . $value . '" />';
365 }
366 }
367 } else {
368 // Fallback
369 if (!empty($class)) {
370 $class = 'class="' . $class . '" ';
371 }
372 $code .= '<textarea name="' . $name . '" ' . $class . $additionalParams . '>' . $content . '</textarea>';
373 }
374 return $code;
375 }
376
377 /**
378 * Save the content from t3editor retrieved via Ajax
379 *
380 * new Ajax.Request('/dev/t3e/dummy/typo3/ajax.php', {
381 * parameters: {
382 * ajaxID: 'T3editor::saveCode',
383 * t3editor_savetype: 'TypoScriptTemplateInformationModuleFunctionController'
384 * }
385 * });
386 *
387 * @param array $params Parameters (not used yet)
388 * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxObj AjaxRequestHandler to handle response
389 */
390 public function ajaxSaveCode($params, $ajaxObj) {
391 // cancel if its not an Ajax request
392 if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
393 $ajaxObj->setContentFormat('json');
394 $codeType = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('t3editor_savetype');
395 $savingsuccess = FALSE;
396 try {
397 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/t3editor/classes/class.tx_t3editor.php']['ajaxSaveCode'])) {
398 $_params = array(
399 'pObj' => &$this,
400 'type' => $codeType,
401 'ajaxObj' => &$ajaxObj
402 );
403 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/t3editor/classes/class.tx_t3editor.php']['ajaxSaveCode'] as $key => $_funcRef) {
404 $savingsuccess = \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($_funcRef, $_params, $this) || $savingsuccess;
405 }
406 }
407 } catch (\Exception $e) {
408 $ajaxObj->setContent(array('result' => FALSE, 'exceptionMessage' => htmlspecialchars($e->getMessage()), 'exceptionCode' => $e->getCode()));
409 return;
410 }
411 $ajaxObj->setContent(array('result' => $savingsuccess));
412 }
413 }
414
415 /**
416 * Gets plugins that are defined at $TYPO3_CONF_VARS['EXTCONF']['t3editor']['plugins']
417 * (called by typo3/ajax.php)
418 *
419 * @param array $params additional parameters (not used here)
420 * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler &$ajaxObj The AjaxRequestHandler object of this request
421 * @return void
422 * @author Oliver Hader <oliver@typo3.org>
423 */
424 public function getPlugins($params, \TYPO3\CMS\Core\Http\AjaxRequestHandler &$ajaxObj) {
425 $result = array();
426 $plugins = &$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3editor']['plugins'];
427 if (is_array($plugins)) {
428 $result = array_values($plugins);
429 }
430 $ajaxObj->setContent($result);
431 $ajaxObj->setContentFormat('jsonbody');
432 }
433
434 }