[TASK] Fix CGL violations against InlineControlStructure
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / pi1 / class.tx_rtehtmlarea_pi1.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2003-2012 Stanislas Rolland <typo3(arobas)sjbr.ca>
6 * All rights reserved
7 *
8 * This script is part of the Typo3 project. The Typo3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24 /**
25 * Spell checking plugin 'tx_rtehtmlarea_pi1' for the htmlArea RTE extension.
26 *
27 * @author Stanislas Rolland <typo3(arobas)sjbr.ca>
28 *
29 */
30
31 class tx_rtehtmlarea_pi1 {
32
33 protected $csConvObj;
34 var $extKey = 'rtehtmlarea'; // The extension key.
35 var $siteUrl;
36 var $charset = 'utf-8';
37 var $parserCharset = 'utf-8';
38 var $defaultAspellEncoding = 'utf-8';
39 var $aspellEncoding;
40 var $result;
41 var $text;
42 var $misspelled = array();
43 var $suggestedWords;
44 var $wordCount = 0;
45 var $suggestionCount = 0;
46 var $suggestedWordCount = 0;
47 var $pspell_link;
48 var $pspellMode = 'normal';
49 var $dictionary;
50 var $AspellDirectory;
51 var $pspell_is_available;
52 var $forceCommandMode = 0;
53 var $filePrefix = 'rtehtmlarea_';
54 // Pre-FAL backward compatibility
55 protected $uploadFolder = 'uploads/tx_rtehtmlarea/';
56 var $personalDictsArg = '';
57 var $xmlCharacterData = '';
58
59 /**
60 * Main class of Spell Checker plugin for Typo3 CMS
61 *
62 * @return string content produced by the plugin
63 */
64 function main() {
65
66 $this->csConvObj = t3lib_div::makeInstance('t3lib_cs');
67
68 // Setting start time
69 $time_start = microtime(TRUE);
70 $this->pspell_is_available = in_array('pspell', get_loaded_extensions());
71 $this->AspellDirectory = trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['plugins']['SpellChecker']['AspellDirectory'])? trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['plugins']['SpellChecker']['AspellDirectory']) : '/usr/bin/aspell';
72 // Setting command mode if requested and available
73 $this->forceCommandMode = (trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['plugins']['SpellChecker']['forceCommandMode']))? trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['plugins']['SpellChecker']['forceCommandMode']) : 0;
74 if (!$this->pspell_is_available || $this->forceCommandMode) {
75 $AspellVersionString = explode('Aspell', shell_exec( $this->AspellDirectory.' -v'));
76 $AspellVersion = substr( $AspellVersionString[1], 0, 4);
77 if( doubleval($AspellVersion) < doubleval('0.5') && (!$this->pspell_is_available || $this->forceCommandMode)) {
78 echo('Configuration problem: Aspell version ' . $AspellVersion . ' too old. Spell checking cannot be performed in command mode.');
79 }
80 $this->defaultAspellEncoding = trim(shell_exec($this->AspellDirectory.' config encoding'));
81 }
82 // Setting the list of dictionaries
83 $dictionaryList = shell_exec( $this->AspellDirectory . ' dump dicts');
84 $dictionaryList = implode(',', t3lib_div::trimExplode(LF, $dictionaryList, 1));
85 $dictionaryArray = t3lib_div::trimExplode(',', $dictionaryList, 1);
86 $restrictToDictionaries = t3lib_div::_POST('restrictToDictionaries');
87 if ($restrictToDictionaries) {
88 $dictionaryArray = array_intersect($dictionaryArray, t3lib_div::trimExplode(',', $restrictToDictionaries, 1));
89 }
90 if (!count($dictionaryArray)) {
91 $dictionaryArray[] = 'en';
92 }
93 $this->dictionary = t3lib_div::_POST('dictionary');
94 $defaultDictionary = $this->dictionary;
95 if (!$defaultDictionary || !in_array($defaultDictionary, $dictionaryArray)) {
96 $defaultDictionary = 'en';
97 }
98 uasort($dictionaryArray, 'strcoll');
99 $dictionaryList = implode(',', $dictionaryArray);
100 // Setting the dictionary
101 if (empty($this->dictionary) || !in_array($this->dictionary, $dictionaryArray)) {
102 $this->dictionary = 'en';
103 }
104 // Setting the pspell suggestion mode
105 $this->pspellMode = t3lib_div::_POST('pspell_mode')?t3lib_div::_POST('pspell_mode'): $this->pspellMode;
106 // Now sanitize $this->pspellMode
107 $this->pspellMode = t3lib_div::inList('ultra,fast,normal,bad-spellers', $this->pspellMode)?$this->pspellMode:'normal';
108 switch($this->pspellMode) {
109 case 'ultra':
110 case 'fast':
111 $pspellModeFlag = PSPELL_FAST;
112 break;
113 case 'bad-spellers':
114 $pspellModeFlag = PSPELL_BAD_SPELLERS;
115 break;
116 case 'normal':
117 default:
118 $pspellModeFlag = PSPELL_NORMAL;
119 break;
120 }
121
122 // Setting the charset
123 if (t3lib_div::_POST('pspell_charset')) {
124 $this->charset = trim(t3lib_div::_POST('pspell_charset'));
125 }
126 if (strtolower($this->charset) == 'iso-8859-1') {
127 $this->parserCharset = strtolower($this->charset);
128 }
129
130 // In some configurations, Aspell uses 'iso8859-1' instead of 'iso-8859-1'
131 $this->aspellEncoding = $this->parserCharset;
132 if ($this->parserCharset == 'iso-8859-1' && strstr($this->defaultAspellEncoding, '8859-1')) {
133 $this->aspellEncoding = $this->defaultAspellEncoding;
134 }
135
136 // However, we are going to work only in the parser charset
137 if($this->pspell_is_available && !$this->forceCommandMode) {
138 $this->pspell_link = pspell_new($this->dictionary, '', '', $this->parserCharset, $pspellModeFlag);
139 }
140
141 // Setting the path to user personal dicts, if any
142 if (t3lib_div::_POST('enablePersonalDicts') == 'true' && TYPO3_MODE == 'BE' && is_object($GLOBALS['BE_USER'])) {
143 if ($GLOBALS['BE_USER']->user['uid']) {
144 $personalDictionaryFolderName = 'BE_' . $GLOBALS['BE_USER']->user['uid'];
145 // Check for pre-FAL personal dictionary folder
146 try {
147 $personalDictionaryFolder = t3lib_file_Factory::getInstance()->getFolderObjectFromCombinedIdentifier(
148 PATH_site . $this->uploadFolder . $personalDictionaryFolderName
149 );
150 } catch (Exception $e) {
151 $personalDictionaryFolder = FALSE;
152 }
153 // The personal dictionary folder is created in the user's default upload folder
154 // and named BE_(uid)_personaldictionary
155 if (!$personalDictionaryFolder) {
156 $personalDictionaryFolderName .= '_personaldictionary';
157 $backendUserDefaultFolder = $GLOBALS['BE_USER']->getDefaultUploadFolder();
158 if ($backendUserDefaultFolder->hasFolder($personalDictionaryFolderName)) {
159 $personalDictionaryFolder = $backendUserDefaultFolder->getSubfolder($personalDictionaryFolderName);
160 } else {
161 $personalDictionaryFolder = $backendUserDefaultFolder->createFolder($personalDictionaryFolderName);
162 }
163 }
164 $personalDictionaryPath = PATH_site . rtrim($personalDictionaryFolder->getPublicUrl(), '/');
165 // Escape here for later use
166 $this->personalDictsArg = ' --home-dir=' . escapeshellarg($personalDictionaryPath);
167 }
168 }
169
170 $cmd = t3lib_div::_POST('cmd');
171 if ($cmd == 'learn') {
172 // Only availble for BE_USERS, die silently if someone has gotten here by accident
173 if (TYPO3_MODE !='BE' || !is_object($GLOBALS['BE_USER'])) {
174 die('');
175 }
176 // Updating the personal word list
177 $to_p_dict = t3lib_div::_POST('to_p_dict');
178 $to_p_dict = $to_p_dict ? $to_p_dict : array();
179 $to_r_list = t3lib_div::_POST('to_r_list');
180 $to_r_list = $to_r_list ? $to_r_list : array();
181 header('Content-Type: text/plain; charset=' . strtoupper($this->parserCharset));
182 header('Pragma: no-cache');
183 if($to_p_dict || $to_r_list) {
184 $tmpFileName = t3lib_div::tempnam($this->filePrefix);
185 if($filehandle = fopen($tmpFileName, 'wb')) {
186 foreach ($to_p_dict as $personal_word) {
187 $cmd = '&' . $personal_word . LF;
188 echo $cmd;
189 fwrite($filehandle, $cmd, strlen($cmd));
190 }
191 foreach ($to_r_list as $replace_pair) {
192 $cmd = '$$ra ' . $replace_pair[0] . ' , ' . $replace_pair[1] . LF;
193 echo $cmd;
194 fwrite($filehandle, $cmd, strlen($cmd));
195 }
196 $cmd = "#\n";
197 echo $cmd;
198 fwrite($filehandle, $cmd, strlen($cmd));
199 fclose($filehandle);
200 // $this->personalDictsArg has already been escapeshellarg()'ed above, it is an optional paramter and might be empty here
201 $AspellCommand = 'cat ' . escapeshellarg($tmpFileName) . ' | ' . $this->AspellDirectory . ' -a --mode=none' . $this->personalDictsArg . ' --lang=' . escapeshellarg($this->dictionary) . ' --encoding=' . escapeshellarg($this->aspellEncoding) . ' 2>&1';
202 print $AspellCommand . LF;
203 print shell_exec($AspellCommand);
204 t3lib_div::unlink_tempfile($tmpFileName);
205 echo('Personal word list was updated.');
206 } else {
207 echo('SpellChecker tempfile open error.');
208 }
209 } else {
210 echo('Nothing to add to the personal word list.');
211 }
212 flush();
213 exit();
214 } else {
215 // Check spelling content
216 // Initialize output
217 $this->result = '<?xml version="1.0" encoding="' . $this->parserCharset . '"?>
218 <!DOCTYPE html
219 PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
220 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
221 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . substr($this->dictionary, 0, 2) . '" lang="' . substr($this->dictionary, 0, 2) . '">
222 <head>
223 <meta http-equiv="Content-Type" content="text/html; charset=' . $this->parserCharset . '" />
224 <link rel="stylesheet" type="text/css" media="all" href="' . (TYPO3_MODE == 'BE' ? '../' : '') . t3lib_extMgm::siteRelPath($this->extKey) . '/htmlarea/plugins/SpellChecker/spell-check-style.css" />
225 <script type="text/javascript">
226 /*<![CDATA[*/
227 <!--
228 ';
229
230 // Getting the input content
231 $content = t3lib_div::_POST('content');
232
233 // Parsing the input HTML
234 $parser = xml_parser_create(strtoupper($this->parserCharset));
235 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
236 xml_set_object($parser, $this);
237 if (!xml_set_element_handler($parser, 'startHandler', 'endHandler')) {
238 echo('Bad xml handler setting');
239 }
240 if (!xml_set_character_data_handler($parser, 'collectDataHandler')) {
241 echo('Bad xml handler setting');
242 }
243 if (!xml_set_default_handler($parser, 'defaultHandler')) {
244 echo('Bad xml handler setting');
245 }
246 if (!xml_parse($parser, '<?xml version="1.0" encoding="' . $this->parserCharset . '"?><spellchecker> ' . preg_replace('/&nbsp;/'.(($this->parserCharset == 'utf-8')?'u':''), ' ', $content) . ' </spellchecker>')) {
247 echo('Bad parsing');
248 }
249 if (xml_get_error_code($parser)) {
250 throw new UnexpectedException('Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser)), 1294585788);
251 }
252 xml_parser_free($parser);
253 if ($this->pspell_is_available && !$this->forceCommandMode) {
254 pspell_clear_session ($this->pspell_link);
255 }
256 $this->result .= 'var suggestedWords = {' . $this->suggestedWords . '};
257 var dictionaries = "' . $dictionaryList . '";
258 var selectedDictionary = "' . $this->dictionary . '";
259 ';
260
261 // Calculating parsing and spell checkting time
262 $time = number_format(microtime(TRUE) - $time_start, 2, ',', ' ');
263
264 // Insert spellcheck info
265 $this->result .= 'var spellcheckInfo = { "Total words":"'.$this->wordCount.'","Misspelled words":"'.sizeof($this->misspelled).'","Total suggestions":"'.$this->suggestionCount.'","Total words suggested":"'.$this->suggestedWordCount.'","Spelling checked in":"'.$time.'" };
266 // -->
267 /*]]>*/
268 </script>
269 </head>
270 ';
271 $this->result .= '<body onload="window.parent.RTEarea[\'' . t3lib_div::_POST('editorId') . '\'].editor.getPlugin(\'SpellChecker\').spellCheckComplete();">';
272 $this->result .= preg_replace('/'.preg_quote('<?xml').'.*'.preg_quote('?>').'['.preg_quote(LF.CR.chr(32)).']*/'.(($this->parserCharset == 'utf-8')?'u':''), '', $this->text);
273 $this->result .= '<div style="display: none;">'.$dictionaries.'</div>';
274 // Closing
275 $this->result .= '
276 </body></html>';
277
278 // Outputting
279 header('Content-Type: text/html; charset=' . strtoupper($this->parserCharset));
280 echo $this->result;
281 }
282
283 } // end of function main
284
285 function startHandler($xml_parser, $tag, $attributes) {
286
287 if (strlen($this->xmlCharacterData)) {
288 $this->spellCheckHandler($xml_parser, $this->xmlCharacterData);
289 $this->xmlCharacterData = '';
290 }
291
292 switch($tag) {
293 case 'spellchecker':
294 break;
295 case 'br':
296 case 'BR':
297 case 'img':
298 case 'IMG':
299 case 'hr':
300 case 'HR':
301 case 'area':
302 case 'AREA':
303 $this->text .= '<'. $this->csConvObj->conv_case($this->parserCharset, $tag, 'toLower') . ' ';
304 foreach( $attributes as $key => $val) {
305 $this->text .= $key . '="' . $val . '" ';
306 }
307 $this->text .= ' />';
308 break;
309 default:
310 $this->text .= '<'. $this->csConvObj->conv_case($this->parserCharset, $tag, 'toLower') . ' ';
311 foreach( $attributes as $key => $val) {
312 $this->text .= $key . '="' . $val . '" ';
313 }
314 $this->text .= '>';
315 break;
316 }
317 return;
318 }
319
320 function endHandler($xml_parser, $tag) {
321 if (strlen($this->xmlCharacterData)) {
322 $this->spellCheckHandler($xml_parser, $this->xmlCharacterData);
323 $this->xmlCharacterData = '';
324 }
325
326 switch($tag) {
327 case 'spellchecker':
328 break;
329 case 'br':
330 case 'BR':
331 case 'img':
332 case 'IMG':
333 case 'hr':
334 case 'HR':
335 case 'input':
336 case 'INPUT':
337 case 'area':
338 case 'AREA':
339 break;
340 default:
341 $this->text .= '</' . $tag . '>';
342 break;
343 }
344 return;
345 }
346
347 function spellCheckHandler($xml_parser, $string) {
348 $incurrent=array();
349 $stringText = $string;
350 $words = preg_split((($this->parserCharset == 'utf-8')?'/\P{L}+/u':'/\W+/'), $stringText);
351 while( list(, $word) = each($words) ) {
352 $word = preg_replace('/ /'.(($this->parserCharset == 'utf-8')?'u':''), '', $word);
353 if( $word && !is_numeric($word)) {
354 if($this->pspell_is_available && !$this->forceCommandMode) {
355 if (!pspell_check($this->pspell_link, $word)) {
356 if(!in_array($word, $this->misspelled)) {
357 if(sizeof($this->misspelled) != 0 ) {
358 $this->suggestedWords .= ',';
359 }
360 $suggest = array();
361 $suggest = pspell_suggest($this->pspell_link, $word);
362 if(sizeof($suggest) != 0 ) {
363 $this->suggestionCount++;
364 $this->suggestedWordCount += sizeof($suggest);
365 }
366 $this->suggestedWords .= '"'.$word.'":"'.implode(',', $suggest).'"';
367 $this->misspelled[] = $word;
368 unset($suggest);
369 }
370 if( !in_array($word, $incurrent) ) {
371 $stringText = preg_replace('/\b'.$word.'\b/'.(($this->parserCharset == 'utf-8')?'u':''), '<span class="htmlarea-spellcheck-error">'.$word.'</span>', $stringText);
372 $incurrent[] = $word;
373 }
374 }
375 } else {
376 $tmpFileName = t3lib_div::tempnam($this->filePrefix);
377 if(!$filehandle = fopen($tmpFileName, 'wb')) {
378 echo('SpellChecker tempfile open error');
379 }
380 if(!fwrite($filehandle, $word)) {
381 echo('SpellChecker tempfile write error');
382 }
383 if(!fclose($filehandle)) {
384 echo('SpellChecker tempfile close error');
385 }
386 $AspellCommand = 'cat ' . escapeshellarg($tmpFileName) . ' | ' . $this->AspellDirectory . ' -a check --mode=none --sug-mode=' . escapeshellarg($this->pspellMode) . $this->personalDictsArg . ' --lang=' . escapeshellarg($this->dictionary) . ' --encoding=' . escapeshellarg($this->aspellEncoding) . ' 2>&1';
387 $AspellAnswer = shell_exec($AspellCommand);
388 $AspellResultLines = array();
389 $AspellResultLines = t3lib_div::trimExplode(LF, $AspellAnswer, 1);
390 if(substr($AspellResultLines[0], 0, 6) == 'Error:') {
391 echo('{' . $AspellAnswer . '}');
392 }
393 t3lib_div::unlink_tempfile($tmpFileName);
394 if (substr($AspellResultLines['1'], 0, 1) != '*') {
395 if (!in_array($word, $this->misspelled)) {
396 if (sizeof($this->misspelled) != 0 ) {
397 $this->suggestedWords .= ',';
398 }
399 $suggest = array();
400 $suggestions = array();
401 if (substr($AspellResultLines['1'], 0, 1) == '&') {
402 $suggestions = t3lib_div::trimExplode(':', $AspellResultLines['1'], 1);
403 $suggest = t3lib_div::trimExplode(',', $suggestions['1'], 1);
404 }
405 if (sizeof($suggest) != 0) {
406 $this->suggestionCount++;
407 $this->suggestedWordCount += sizeof($suggest);
408 }
409 $this->suggestedWords .= '"'.$word.'":"'.implode(',', $suggest).'"';
410 $this->misspelled[] = $word;
411 unset($suggest);
412 unset($suggestions);
413 }
414 if (!in_array($word, $incurrent)) {
415 $stringText = preg_replace('/\b'.$word.'\b/'.(($this->parserCharset == 'utf-8')?'u':''), '<span class="htmlarea-spellcheck-error">'.$word.'</span>', $stringText);
416 $incurrent[] = $word;
417 }
418 }
419 unset($AspellResultLines);
420 }
421 $this->wordCount++;
422 }
423 }
424 $this->text .= $stringText;
425 unset($incurrent);
426 return;
427 }
428
429 function collectDataHandler($xml_parser, $string) {
430 $this->xmlCharacterData .= $string;
431 }
432
433 function defaultHandler($xml_parser, $string) {
434 $this->text .= $string;
435 return;
436 }
437 }
438
439 if (TYPO3_MODE=='FE') {
440 tslib_eidtools::connectDB();
441 $spellChecker = t3lib_div::makeInstance('tx_rtehtmlarea_pi1');
442 $spellChecker->main();
443 }
444
445 ?>