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