[SECURITY] XML entity expansion
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Classes / Controller / SpellCheckingController.php
1 <?php
2 namespace TYPO3\CMS\Rtehtmlarea\Controller;
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 TYPO3\CMS\Core\Utility\GeneralUtility;
18
19 /**
20 * Spell checking plugin 'tx_rtehtmlarea_pi1' for the htmlArea RTE extension.
21 *
22 * @author Stanislas Rolland <typo3(arobas)sjbr.ca>
23 */
24 class SpellCheckingController {
25
26 /**
27 * @var \TYPO3\CMS\Core\Charset\CharsetConverter
28 */
29 protected $csConvObj;
30
31 // The extension key
32 /**
33 * @todo Define visibility
34 */
35 public $extKey = 'rtehtmlarea';
36
37 /**
38 * @todo Define visibility
39 */
40 public $siteUrl;
41
42 /**
43 * @todo Define visibility
44 */
45 public $charset = 'utf-8';
46
47 /**
48 * @todo Define visibility
49 */
50 public $parserCharset = 'utf-8';
51
52 /**
53 * @todo Define visibility
54 */
55 public $defaultAspellEncoding = 'utf-8';
56
57 /**
58 * @todo Define visibility
59 */
60 public $aspellEncoding;
61
62 /**
63 * @todo Define visibility
64 */
65 public $result;
66
67 /**
68 * @todo Define visibility
69 */
70 public $text;
71
72 /**
73 * @todo Define visibility
74 */
75 public $misspelled = array();
76
77 /**
78 * @todo Define visibility
79 */
80 public $suggestedWords;
81
82 /**
83 * @todo Define visibility
84 */
85 public $wordCount = 0;
86
87 /**
88 * @todo Define visibility
89 */
90 public $suggestionCount = 0;
91
92 /**
93 * @todo Define visibility
94 */
95 public $suggestedWordCount = 0;
96
97 /**
98 * @todo Define visibility
99 */
100 public $pspell_link;
101
102 /**
103 * @todo Define visibility
104 */
105 public $pspellMode = 'normal';
106
107 /**
108 * @todo Define visibility
109 */
110 public $dictionary;
111
112 /**
113 * @todo Define visibility
114 */
115 public $AspellDirectory;
116
117 /**
118 * @todo Define visibility
119 */
120 public $pspell_is_available;
121
122 /**
123 * @todo Define visibility
124 */
125 public $forceCommandMode = 0;
126
127 /**
128 * @todo Define visibility
129 */
130 public $filePrefix = 'rtehtmlarea_';
131
132 // Pre-FAL backward compatibility
133 protected $uploadFolder = 'uploads/tx_rtehtmlarea/';
134
135 // Path to main dictionary
136 protected $mainDictionaryPath;
137
138 // Path to personal dictionary
139 protected $personalDictionaryPath;
140
141 /**
142 * @todo Define visibility
143 */
144 public $xmlCharacterData = '';
145
146 /**
147 * Main class of Spell Checker plugin for Typo3 CMS
148 *
149 * @return string content produced by the plugin
150 * @todo Define visibility
151 */
152 public function main() {
153 $this->csConvObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Charset\\CharsetConverter');
154 // Setting start time
155 $time_start = microtime(TRUE);
156 $this->pspell_is_available = in_array('pspell', get_loaded_extensions());
157 $this->AspellDirectory = trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['plugins']['SpellChecker']['AspellDirectory']) ?: '/usr/bin/aspell';
158 // Setting command mode if requested and available
159 $this->forceCommandMode = trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['plugins']['SpellChecker']['forceCommandMode']) ?: 0;
160 if (!$this->pspell_is_available || $this->forceCommandMode) {
161 $AspellVersionString = explode('Aspell', shell_exec($this->AspellDirectory . ' -v'));
162 $AspellVersion = substr($AspellVersionString[1], 0, 4);
163 if (doubleval($AspellVersion) < doubleval('0.5') && (!$this->pspell_is_available || $this->forceCommandMode)) {
164 echo 'Configuration problem: Aspell version ' . $AspellVersion . ' too old. Spell checking cannot be performed in command mode.';
165 }
166 $this->defaultAspellEncoding = trim(shell_exec($this->AspellDirectory . ' config encoding'));
167 }
168 // Setting the list of dictionaries
169 $dictionaryList = shell_exec($this->AspellDirectory . ' dump dicts');
170 $dictionaryList = implode(',', GeneralUtility::trimExplode(LF, $dictionaryList, TRUE));
171 $dictionaryArray = GeneralUtility::trimExplode(',', $dictionaryList, TRUE);
172 $restrictToDictionaries = GeneralUtility::_POST('restrictToDictionaries');
173 if ($restrictToDictionaries) {
174 $dictionaryArray = array_intersect($dictionaryArray, GeneralUtility::trimExplode(',', $restrictToDictionaries, 1));
175 }
176 if (!count($dictionaryArray)) {
177 $dictionaryArray[] = 'en';
178 }
179 $this->dictionary = GeneralUtility::_POST('dictionary');
180 $defaultDictionary = $this->dictionary;
181 if (!$defaultDictionary || !in_array($defaultDictionary, $dictionaryArray)) {
182 $defaultDictionary = 'en';
183 }
184 uasort($dictionaryArray, 'strcoll');
185 $dictionaryList = implode(',', $dictionaryArray);
186 // Setting the dictionary
187 if (empty($this->dictionary) || !in_array($this->dictionary, $dictionaryArray)) {
188 $this->dictionary = 'en';
189 }
190 // Setting the pspell suggestion mode
191 $this->pspellMode = GeneralUtility::_POST('pspell_mode') ? GeneralUtility::_POST('pspell_mode') : $this->pspellMode;
192 // Now sanitize $this->pspellMode
193 $this->pspellMode = GeneralUtility::inList('ultra,fast,normal,bad-spellers', $this->pspellMode) ? $this->pspellMode : 'normal';
194 switch ($this->pspellMode) {
195 case 'ultra':
196
197 case 'fast':
198 $pspellModeFlag = PSPELL_FAST;
199 break;
200 case 'bad-spellers':
201 $pspellModeFlag = PSPELL_BAD_SPELLERS;
202 break;
203 case 'normal':
204
205 default:
206 $pspellModeFlag = PSPELL_NORMAL;
207 }
208 // Setting the charset
209 if (GeneralUtility::_POST('pspell_charset')) {
210 $this->charset = trim(GeneralUtility::_POST('pspell_charset'));
211 }
212 if (strtolower($this->charset) == 'iso-8859-1') {
213 $this->parserCharset = strtolower($this->charset);
214 }
215 // In some configurations, Aspell uses 'iso8859-1' instead of 'iso-8859-1'
216 $this->aspellEncoding = $this->parserCharset;
217 if ($this->parserCharset == 'iso-8859-1' && strstr($this->defaultAspellEncoding, '8859-1')) {
218 $this->aspellEncoding = $this->defaultAspellEncoding;
219 }
220 // However, we are going to work only in the parser charset
221 if ($this->pspell_is_available && !$this->forceCommandMode) {
222 $this->pspell_link = pspell_new($this->dictionary, '', '', $this->parserCharset, $pspellModeFlag);
223 }
224 // Setting the path to main dictionary
225 $this->setMainDictionaryPath();
226 // Setting the path to user personal dictionary, if any
227 $this->setPersonalDictionaryPath();
228 $this->fixPersonalDictionaryCharacterSet();
229 $cmd = GeneralUtility::_POST('cmd');
230 if ($cmd == 'learn') {
231 // Only availble for BE_USERS, die silently if someone has gotten here by accident
232 if (TYPO3_MODE !== 'BE' || !is_object($GLOBALS['BE_USER'])) {
233 die('');
234 }
235 // Updating the personal word list
236 $to_p_dict = GeneralUtility::_POST('to_p_dict');
237 $to_p_dict = $to_p_dict ? $to_p_dict : array();
238 $to_r_list = GeneralUtility::_POST('to_r_list');
239 $to_r_list = $to_r_list ? $to_r_list : array();
240 header('Content-Type: text/plain; charset=' . strtoupper($this->parserCharset));
241 header('Pragma: no-cache');
242 if ($to_p_dict || $to_r_list) {
243 $tmpFileName = GeneralUtility::tempnam($this->filePrefix);
244 $filehandle = fopen($tmpFileName, 'wb');
245 if ($filehandle) {
246 // Get the character set of the main dictionary
247 // We need to convert the input into the character set of the main dictionary
248 $mainDictionaryCharacterSet = $this->getMainDictionaryCharacterSet();
249 // Write the personal words addition commands to the temporary file
250 foreach ($to_p_dict as $personal_word) {
251 $cmd = '&' . $this->csConvObj->conv($personal_word, $this->parserCharset, $mainDictionaryCharacterSet) . LF;
252 fwrite($filehandle, $cmd, strlen($cmd));
253 }
254 // Write the replacent pairs addition commands to the temporary file
255 foreach ($to_r_list as $replace_pair) {
256 $cmd = '$$ra ' . $this->csConvObj->conv($replace_pair[0], $this->parserCharset, $mainDictionaryCharacterSet) . ' , ' . $this->csConvObj->conv($replace_pair[1], $this->parserCharset, $mainDictionaryCharacterSet) . LF;
257 fwrite($filehandle, $cmd, strlen($cmd));
258 }
259 $cmd = '#' . LF;
260 $result = fwrite($filehandle, $cmd, strlen($cmd));
261 if ($result === FALSE) {
262 GeneralUtility::sysLog('SpellChecker tempfile write error: ' . $tmpFileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
263 } else {
264 // Assemble the Aspell command
265 $aspellCommand = ((TYPO3_OS === 'WIN') ? 'type ' : 'cat ') . escapeshellarg($tmpFileName) . ' | '
266 . $this->AspellDirectory
267 . ' -a --mode=none'
268 . ($this->personalDictionaryPath ? ' --home-dir=' . escapeshellarg($this->personalDictionaryPath) : '')
269 . ' --lang=' . escapeshellarg($this->dictionary)
270 . ' --encoding=' . escapeshellarg($mainDictionaryCharacterSet)
271 . ' 2>&1';
272 $aspellResult = shell_exec($aspellCommand);
273 // Close and delete the temporary file
274 fclose($filehandle);
275 GeneralUtility::unlink_tempfile($tmpFileName);
276 }
277 } else {
278 GeneralUtility::sysLog('SpellChecker tempfile open error: ' . $tmpFileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
279 }
280 }
281 flush();
282 die;
283 } else {
284 // Check spelling content
285 // Initialize output
286 $this->result = '<?xml version="1.0" encoding="' . $this->parserCharset . '"?>
287 <!DOCTYPE html
288 PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
289 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
290 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . substr($this->dictionary, 0, 2) . '" lang="' . substr($this->dictionary, 0, 2) . '">
291 <head>
292 <meta http-equiv="Content-Type" content="text/html; charset=' . $this->parserCharset . '" />
293 <link rel="stylesheet" type="text/css" media="all" href="' . (TYPO3_MODE == 'BE' ? '../' : '') . \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath($this->extKey) . '/htmlarea/plugins/SpellChecker/spell-check-style.css" />
294 <script type="text/javascript">
295 /*<![CDATA[*/
296 <!--
297 ';
298 // Getting the input content
299 $content = GeneralUtility::_POST('content');
300 // Parsing the input HTML
301 $parser = xml_parser_create(strtoupper($this->parserCharset));
302 // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
303 $previousValueOfEntityLoader = libxml_disable_entity_loader(TRUE);
304 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
305 xml_set_object($parser, $this);
306 if (!xml_set_element_handler($parser, 'startHandler', 'endHandler')) {
307 echo 'Bad xml handler setting';
308 }
309 if (!xml_set_character_data_handler($parser, 'collectDataHandler')) {
310 echo 'Bad xml handler setting';
311 }
312 if (!xml_set_default_handler($parser, 'defaultHandler')) {
313 echo 'Bad xml handler setting';
314 }
315 if (!xml_parse($parser, ('<?xml version="1.0" encoding="' . $this->parserCharset . '"?><spellchecker> ' . preg_replace(('/&nbsp;/' . ($this->parserCharset == 'utf-8' ? 'u' : '')), ' ', $content) . ' </spellchecker>'))) {
316 echo 'Bad parsing';
317 }
318 if (xml_get_error_code($parser)) {
319 throw new \UnexpectedException('Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser)), 1294585788);
320 }
321 libxml_disable_entity_loader($previousValueOfEntityLoader);
322 xml_parser_free($parser);
323 if ($this->pspell_is_available && !$this->forceCommandMode) {
324 pspell_clear_session($this->pspell_link);
325 }
326 $this->result .= 'var suggestedWords = {' . $this->suggestedWords . '};
327 var dictionaries = "' . $dictionaryList . '";
328 var selectedDictionary = "' . $this->dictionary . '";
329 ';
330 // Calculating parsing and spell checkting time
331 $time = number_format(microtime(TRUE) - $time_start, 2, ',', ' ');
332 // Insert spellcheck info
333 $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 . '" };
334 // -->
335 /*]]>*/
336 </script>
337 </head>
338 ';
339 $this->result .= '<body onload="window.parent.RTEarea[' . GeneralUtility::quoteJSvalue(GeneralUtility::_POST('editorId')) . '].editor.getPlugin(\'SpellChecker\').spellCheckComplete();">';
340 $this->result .= preg_replace('/' . preg_quote('<?xml') . '.*' . preg_quote('?>') . '[' . preg_quote((LF . CR . chr(32))) . ']*/' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '', $this->text);
341 $this->result .= '<div style="display: none;">' . $dictionaries . '</div>';
342 // Closing
343 $this->result .= '
344 </body></html>';
345 // Outputting
346 header('Content-Type: text/html; charset=' . strtoupper($this->parserCharset));
347 echo $this->result;
348 }
349 }
350
351 /**
352 * Sets the path to the main dictionary
353 *
354 * @return string path to the main dictionary
355 */
356 protected function setMainDictionaryPath() {
357 $this->mainDictionaryPath = '';
358 $aspellCommand = $this->AspellDirectory . ' config dict-dir';
359 $aspellResult = shell_exec($aspellCommand);
360 if ($aspellResult) {
361 $this->mainDictionaryPath = trim($aspellResult);
362 }
363 if (!$aspellResult || !$this->mainDictionaryPath) {
364 GeneralUtility::sysLog('SpellChecker main dictionary path retrieval error: ' . $aspellCommand, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
365 }
366 return $this->mainDictionaryPath;
367 }
368
369 /**
370 * Gets the character set the main dictionary
371 *
372 * @return string character set the main dictionary
373 */
374 protected function getMainDictionaryCharacterSet() {
375 $characterSet = '';
376 if ($this->mainDictionaryPath) {
377 // Keep only the first part of the dictionary name
378 $mainDictionary = preg_split('/[-_]/', $this->dictionary, 2);
379 // Read the options of the dictionary
380 $dictionaryFileName = $this->mainDictionaryPath . '/' . $mainDictionary[0] . '.dat';
381 $dictionaryHandle = fopen($dictionaryFileName, 'rb');
382 if (!$dictionaryHandle) {
383 GeneralUtility::sysLog('SpellChecker main dictionary open error: ' . $dictionaryFileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
384 } else {
385 $dictionaryContent = fread($dictionaryHandle, 500);
386 if ($dictionaryContent === FALSE) {
387 GeneralUtility::sysLog('SpellChecker main dictionary read error: ' . $dictionaryFileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
388 } else {
389 fclose($dictionaryHandle);
390 // Get the line that contains the character set option
391 $dictionaryContent = preg_split('/charset\s*/', $dictionaryContent, 2);
392 if ($dictionaryContent[1]) {
393 // Isolate the character set
394 $dictionaryContent = GeneralUtility::trimExplode(LF, $dictionaryContent[1]);
395 // Fix Aspell character set oddity (i.e. iso8859-1)
396 $characterSet = str_replace(
397 array('iso', '--'),
398 array('iso-', '-'),
399 $dictionaryContent[0]
400 );
401 }
402 if (!$characterSet) {
403 GeneralUtility::sysLog('SpellChecker main dictionary character set retrieval error: ' . $dictionaryContent[1], $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
404 }
405 }
406 }
407 }
408 return $characterSet;
409 }
410
411 /**
412 * Sets the path to the personal dictionary
413 *
414 * @return string path to the personal dictionary
415 */
416 protected function setPersonalDictionaryPath() {
417 $this->personalDictionaryPath = '';
418 if (GeneralUtility::_POST('enablePersonalDicts') == 'true' && TYPO3_MODE == 'BE' && is_object($GLOBALS['BE_USER'])) {
419 if ($GLOBALS['BE_USER']->user['uid']) {
420 $personalDictionaryFolderName = 'BE_' . $GLOBALS['BE_USER']->user['uid'];
421 // Check for pre-FAL personal dictionary folder
422 try {
423 $personalDictionaryFolder = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier(PATH_site . $this->uploadFolder . $personalDictionaryFolderName);
424 } catch (\Exception $e) {
425 $personalDictionaryFolder = FALSE;
426 }
427 // The personal dictionary folder is created in the user's default upload folder and named BE_(uid)_personaldictionary
428 if (!$personalDictionaryFolder) {
429 $personalDictionaryFolderName .= '_personaldictionary';
430 $backendUserDefaultFolder = $GLOBALS['BE_USER']->getDefaultUploadFolder();
431 if ($backendUserDefaultFolder->hasFolder($personalDictionaryFolderName)) {
432 $personalDictionaryFolder = $backendUserDefaultFolder->getSubfolder($personalDictionaryFolderName);
433 } else {
434 $personalDictionaryFolder = $backendUserDefaultFolder->createFolder($personalDictionaryFolderName);
435 }
436 }
437 $this->personalDictionaryPath = PATH_site . rtrim($personalDictionaryFolder->getPublicUrl(), '/');
438 }
439 }
440 return $this->personalDictionaryPath;
441 }
442
443 /**
444 * Ensures that the personal dictionary is utf-8 encoded
445 *
446 * @return void
447 */
448 protected function fixPersonalDictionaryCharacterSet() {
449 if ($this->personalDictionaryPath) {
450 // Fix the options of the personl word list and of the replacement pairs files
451 // Aspell creates such files only for the main dictionary
452 $fileNames = array();
453 $mainDictionary = preg_split('/[-_]/', $this->dictionary, 2);
454 $fileNames[0] = $this->personalDictionaryPath . '/' . '.aspell.' . $mainDictionary[0] . '.pws';
455 $fileNames[1] = $this->personalDictionaryPath . '/' . '.aspell.' . $mainDictionary[0] . '.prepl';
456 foreach ($fileNames as $fileName) {
457 if (file_exists($fileName)) {
458 $fileContent = file_get_contents($fileName);
459 if ($fileContent === FALSE) {
460 GeneralUtility::sysLog('SpellChecker personal word list read error: ' . $fileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
461 } else {
462 $fileContent = explode(LF, $fileContent);
463 if (strpos($fileContent[0], 'utf-8') === FALSE) {
464 $fileContent[0] .= ' utf-8';
465 $fileContent = implode(LF, $fileContent);
466 $result = file_put_contents($fileName, $fileContent);
467 if ($result === FALSE) {
468 GeneralUtility::sysLog('SpellChecker personal word list write error: ' . $fileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
469 }
470 }
471 }
472 }
473 }
474 }
475 }
476
477 /**
478 * @todo Define visibility
479 */
480 public function startHandler($xml_parser, $tag, $attributes) {
481 if (strlen($this->xmlCharacterData)) {
482 $this->spellCheckHandler($xml_parser, $this->xmlCharacterData);
483 $this->xmlCharacterData = '';
484 }
485 switch ($tag) {
486 case 'spellchecker':
487 break;
488 case 'br':
489
490 case 'BR':
491
492 case 'img':
493
494 case 'IMG':
495
496 case 'hr':
497
498 case 'HR':
499
500 case 'area':
501
502 case 'AREA':
503 $this->text .= '<' . $this->csConvObj->conv_case($this->parserCharset, $tag, 'toLower') . ' ';
504 foreach ($attributes as $key => $val) {
505 $this->text .= $key . '="' . $val . '" ';
506 }
507 $this->text .= ' />';
508 break;
509 default:
510 $this->text .= '<' . $this->csConvObj->conv_case($this->parserCharset, $tag, 'toLower') . ' ';
511 foreach ($attributes as $key => $val) {
512 $this->text .= $key . '="' . $val . '" ';
513 }
514 $this->text .= '>';
515 }
516 }
517
518 /**
519 * @todo Define visibility
520 */
521 public function endHandler($xml_parser, $tag) {
522 if (strlen($this->xmlCharacterData)) {
523 $this->spellCheckHandler($xml_parser, $this->xmlCharacterData);
524 $this->xmlCharacterData = '';
525 }
526 switch ($tag) {
527 case 'spellchecker':
528
529 case 'br':
530
531 case 'BR':
532
533 case 'img':
534
535 case 'IMG':
536
537 case 'hr':
538
539 case 'HR':
540
541 case 'input':
542
543 case 'INPUT':
544
545 case 'area':
546
547 case 'AREA':
548 break;
549 default:
550 $this->text .= '</' . $tag . '>';
551 }
552 }
553
554 /**
555 * @todo Define visibility
556 */
557 public function spellCheckHandler($xml_parser, $string) {
558 $incurrent = array();
559 $stringText = $string;
560 $words = preg_split($this->parserCharset == 'utf-8' ? '/\\P{L}+/u' : '/\\W+/', $stringText);
561 while (list(, $word) = each($words)) {
562 $word = preg_replace('/ /' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '', $word);
563 if ($word && !is_numeric($word)) {
564 if ($this->pspell_is_available && !$this->forceCommandMode) {
565 if (!pspell_check($this->pspell_link, $word)) {
566 if (!in_array($word, $this->misspelled)) {
567 if (sizeof($this->misspelled) != 0) {
568 $this->suggestedWords .= ',';
569 }
570 $suggest = array();
571 $suggest = pspell_suggest($this->pspell_link, $word);
572 if (sizeof($suggest) != 0) {
573 $this->suggestionCount++;
574 $this->suggestedWordCount += sizeof($suggest);
575 }
576 $this->suggestedWords .= '"' . $word . '":"' . implode(',', $suggest) . '"';
577 $this->misspelled[] = $word;
578 unset($suggest);
579 }
580 if (!in_array($word, $incurrent)) {
581 $stringText = preg_replace('/\\b' . $word . '\\b/' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '<span class="htmlarea-spellcheck-error">' . $word . '</span>', $stringText);
582 $incurrent[] = $word;
583 }
584 }
585 } else {
586 $tmpFileName = GeneralUtility::tempnam($this->filePrefix);
587 if (!($filehandle = fopen($tmpFileName, 'wb'))) {
588 echo 'SpellChecker tempfile open error';
589 }
590 if (!fwrite($filehandle, $word)) {
591 echo 'SpellChecker tempfile write error';
592 }
593 if (!fclose($filehandle)) {
594 echo 'SpellChecker tempfile close error';
595 }
596 $catCommand = TYPO3_OS == 'WIN' ? 'type' : 'cat';
597 $AspellCommand = $catCommand . ' ' . escapeshellarg($tmpFileName) . ' | ' . $this->AspellDirectory . ' -a check --mode=none --sug-mode=' . escapeshellarg($this->pspellMode) . ($this->personalDictionaryPath ? ' --home-dir=' . escapeshellarg($this->personalDictionaryPath) : '') . ' --lang=' . escapeshellarg($this->dictionary) . ' --encoding=' . escapeshellarg($this->aspellEncoding) . ' 2>&1';
598 $AspellAnswer = shell_exec($AspellCommand);
599 $AspellResultLines = array();
600 $AspellResultLines = GeneralUtility::trimExplode(LF, $AspellAnswer, TRUE);
601 if (substr($AspellResultLines[0], 0, 6) == 'Error:') {
602 echo '{' . $AspellAnswer . '}';
603 }
604 GeneralUtility::unlink_tempfile($tmpFileName);
605 if ($AspellResultLines['1'][0] !== '*') {
606 if (!in_array($word, $this->misspelled)) {
607 if (sizeof($this->misspelled) != 0) {
608 $this->suggestedWords .= ',';
609 }
610 $suggest = array();
611 $suggestions = array();
612 if ($AspellResultLines['1'][0] === '&') {
613 $suggestions = GeneralUtility::trimExplode(':', $AspellResultLines['1'], TRUE);
614 $suggest = GeneralUtility::trimExplode(',', $suggestions['1'], TRUE);
615 }
616 if (sizeof($suggest) != 0) {
617 $this->suggestionCount++;
618 $this->suggestedWordCount += sizeof($suggest);
619 }
620 $this->suggestedWords .= '"' . $word . '":"' . implode(',', $suggest) . '"';
621 $this->misspelled[] = $word;
622 unset($suggest);
623 unset($suggestions);
624 }
625 if (!in_array($word, $incurrent)) {
626 $stringText = preg_replace('/\\b' . $word . '\\b/' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '<span class="htmlarea-spellcheck-error">' . $word . '</span>', $stringText);
627 $incurrent[] = $word;
628 }
629 }
630 unset($AspellResultLines);
631 }
632 $this->wordCount++;
633 }
634 }
635 $this->text .= $stringText;
636 unset($incurrent);
637 }
638
639 /**
640 * @todo Define visibility
641 */
642 public function collectDataHandler($xml_parser, $string) {
643 $this->xmlCharacterData .= $string;
644 }
645
646 /**
647 * @todo Define visibility
648 */
649 public function defaultHandler($xml_parser, $string) {
650 $this->text .= $string;
651 }
652
653 }