dcf2789e26fba50eaf10b2186a8672467cc69ed8
[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\Http\ControllerInterface;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Spell checking plugin 'tx_rtehtmlarea_pi1' for the htmlArea RTE extension.
24 */
25 class SpellCheckingController implements ControllerInterface {
26
27 /**
28 * @var \TYPO3\CMS\Core\Charset\CharsetConverter
29 */
30 protected $csConvObj;
31
32 // The extension key
33 /**
34 * @var string
35 */
36 public $extKey = 'rtehtmlarea';
37
38 /**
39 * @var string
40 */
41 public $siteUrl;
42
43 /**
44 * @var string
45 */
46 public $charset = 'utf-8';
47
48 /**
49 * @var string
50 */
51 public $parserCharset = 'utf-8';
52
53 /**
54 * @var string
55 */
56 public $defaultAspellEncoding = 'utf-8';
57
58 /**
59 * @var string
60 */
61 public $aspellEncoding;
62
63 /**
64 * @var string
65 */
66 public $result;
67
68 /**
69 * @var string
70 */
71 public $text;
72
73 /**
74 * @var array
75 */
76 public $misspelled = array();
77
78 /**
79 * @var array
80 */
81 public $suggestedWords;
82
83 /**
84 * @var int
85 */
86 public $wordCount = 0;
87
88 /**
89 * @var int
90 */
91 public $suggestionCount = 0;
92
93 /**
94 * @var int
95 */
96 public $suggestedWordCount = 0;
97
98 /**
99 * @var int
100 */
101 public $pspell_link;
102
103 /**
104 * @var string
105 */
106 public $pspellMode = 'normal';
107
108 /**
109 * @var string
110 */
111 public $dictionary;
112
113 /**
114 * @var string
115 */
116 public $AspellDirectory;
117
118 /**
119 * @var bool
120 */
121 public $pspell_is_available;
122
123 /**
124 * @var bool
125 */
126 public $forceCommandMode = 0;
127
128 /**
129 * @var string
130 */
131 public $filePrefix = 'rtehtmlarea_';
132
133 // Pre-FAL backward compatibility
134 protected $uploadFolder = 'uploads/tx_rtehtmlarea/';
135
136 // Path to main dictionary
137 protected $mainDictionaryPath;
138
139 // Path to personal dictionary
140 protected $personalDictionaryPath;
141
142 /**
143 * @var string
144 */
145 public $xmlCharacterData = '';
146
147
148 /**
149 * AJAX entry point
150 *
151 * @param array $ajaxParams
152 * @return void
153 * @throws \UnexpectedValueException
154 */
155 public function main(array $ajaxParams) {
156 $this->processRequest($ajaxParams['request']);
157 header('Content-Type: text/html; charset=' . strtoupper($this->parserCharset));
158 echo $this->result;
159 }
160
161 /**
162 * Main class of Spell Checker plugin for Typo3 CMS
163 *
164 * @param ServerRequestInterface $request
165 * @return ResponseInterface
166 * @throws \InvalidArgumentException
167 * @throws \UnexpectedValueException
168 */
169 public function processRequest(ServerRequestInterface $request) {
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 // Now sanitize $this->pspellMode
210 $this->pspellMode = GeneralUtility::inList('ultra,fast,normal,bad-spellers', $this->pspellMode) ? $this->pspellMode : 'normal';
211 switch ($this->pspellMode) {
212 case 'ultra':
213
214 case 'fast':
215 $pspellModeFlag = PSPELL_FAST;
216 break;
217 case 'bad-spellers':
218 $pspellModeFlag = PSPELL_BAD_SPELLERS;
219 break;
220 case 'normal':
221
222 default:
223 $pspellModeFlag = PSPELL_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 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
320 xml_set_object($parser, $this);
321 if (!xml_set_element_handler($parser, 'startHandler', 'endHandler')) {
322 echo 'Bad xml handler setting';
323 }
324 if (!xml_set_character_data_handler($parser, 'collectDataHandler')) {
325 echo 'Bad xml handler setting';
326 }
327 if (!xml_set_default_handler($parser, 'defaultHandler')) {
328 echo 'Bad xml handler setting';
329 }
330 if (!xml_parse($parser, ('<?xml version="1.0" encoding="' . $this->parserCharset . '"?><spellchecker> ' . preg_replace(('/&nbsp;/' . ($this->parserCharset == 'utf-8' ? 'u' : '')), ' ', $content) . ' </spellchecker>'))) {
331 echo 'Bad parsing';
332 }
333 if (xml_get_error_code($parser)) {
334 throw new \UnexpectedValueException('Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser)), 1294585788);
335 }
336 xml_parser_free($parser);
337 if ($this->pspell_is_available && !$this->forceCommandMode) {
338 pspell_clear_session($this->pspell_link);
339 }
340 $this->result .= 'var suggestedWords = {' . $this->suggestedWords . '};
341 var dictionaries = "' . $dictionaryList . '";
342 var selectedDictionary = "' . $this->dictionary . '";
343 ';
344 // Calculating parsing and spell checkting time
345 $time = number_format(microtime(TRUE) - $time_start, 2, ',', ' ');
346 // Insert spellcheck info
347 $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 . '" };
348 // -->
349 /*]]>*/
350 </script>
351 </head>
352 ';
353 $this->result .= '<body onload="window.parent.RTEarea[\'' . GeneralUtility::_POST('editorId') . '\'].editor.getPlugin(\'SpellChecker\').spellCheckComplete();">';
354 $this->result .= preg_replace('/' . preg_quote('<?xml') . '.*' . preg_quote('?>') . '[' . preg_quote((LF . CR . chr(32))) . ']*/' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '', $this->text);
355 $this->result .= '<div style="display: none;">' . $dictionaries . '</div>';
356 // Closing
357 $this->result .= '
358 </body></html>';
359 // Outputting
360 $response = GeneralUtility::makeInstance(Response::class);
361 $response = $response->withHeader('Content-Type', 'text/html; charset=' . strtoupper($this->parserCharset));
362 $response->getBody()->write($this->result);
363 return $response;
364 }
365 }
366
367 /**
368 * Sets the path to the main dictionary
369 *
370 * @return string path to the main dictionary
371 */
372 protected function setMainDictionaryPath() {
373 $this->mainDictionaryPath = '';
374 $aspellCommand = $this->AspellDirectory . ' config dict-dir';
375 $aspellResult = shell_exec($aspellCommand);
376 if ($aspellResult) {
377 $this->mainDictionaryPath = trim($aspellResult);
378 }
379 if (!$aspellResult || !$this->mainDictionaryPath) {
380 GeneralUtility::sysLog('SpellChecker main dictionary path retrieval error: ' . $aspellCommand, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
381 }
382 return $this->mainDictionaryPath;
383 }
384
385 /**
386 * Gets the character set the main dictionary
387 *
388 * @return string character set the main dictionary
389 */
390 protected function getMainDictionaryCharacterSet() {
391 $characterSet = '';
392 if ($this->mainDictionaryPath) {
393 // Keep only the first part of the dictionary name
394 $mainDictionary = preg_split('/[-_]/', $this->dictionary, 2);
395 // Read the options of the dictionary
396 $dictionaryFileName = $this->mainDictionaryPath . '/' . $mainDictionary[0] . '.dat';
397 $dictionaryHandle = fopen($dictionaryFileName, 'rb');
398 if (!$dictionaryHandle) {
399 GeneralUtility::sysLog('SpellChecker main dictionary open error: ' . $dictionaryFileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
400 } else {
401 $dictionaryContent = fread($dictionaryHandle, 500);
402 if ($dictionaryContent === FALSE) {
403 GeneralUtility::sysLog('SpellChecker main dictionary read error: ' . $dictionaryFileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
404 } else {
405 fclose($dictionaryHandle);
406 // Get the line that contains the character set option
407 $dictionaryContent = preg_split('/charset\s*/', $dictionaryContent, 2);
408 if ($dictionaryContent[1]) {
409 // Isolate the character set
410 $dictionaryContent = GeneralUtility::trimExplode(LF, $dictionaryContent[1]);
411 // Fix Aspell character set oddity (i.e. iso8859-1)
412 $characterSet = str_replace(
413 array('iso', '--'),
414 array('iso-', '-'),
415 $dictionaryContent[0]
416 );
417 }
418 if (!$characterSet) {
419 GeneralUtility::sysLog('SpellChecker main dictionary character set retrieval error: ' . $dictionaryContent[1], $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
420 }
421 }
422 }
423 }
424 return $characterSet;
425 }
426
427 /**
428 * Sets the path to the personal dictionary
429 *
430 * @return string path to the personal dictionary
431 */
432 protected function setPersonalDictionaryPath() {
433 $this->personalDictionaryPath = '';
434 if (GeneralUtility::_POST('enablePersonalDicts') == 'true' && TYPO3_MODE == 'BE' && is_object($GLOBALS['BE_USER'])) {
435 if ($GLOBALS['BE_USER']->user['uid']) {
436 $personalDictionaryFolderName = 'BE_' . $GLOBALS['BE_USER']->user['uid'];
437 // Check for pre-FAL personal dictionary folder
438 try {
439 $personalDictionaryFolder = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier(PATH_site . $this->uploadFolder . $personalDictionaryFolderName);
440 } catch (\Exception $e) {
441 $personalDictionaryFolder = FALSE;
442 }
443 // The personal dictionary folder is created in the user's default upload folder and named BE_(uid)_personaldictionary
444 if (!$personalDictionaryFolder) {
445 $personalDictionaryFolderName .= '_personaldictionary';
446 $backendUserDefaultFolder = $GLOBALS['BE_USER']->getDefaultUploadFolder();
447 if ($backendUserDefaultFolder->hasFolder($personalDictionaryFolderName)) {
448 $personalDictionaryFolder = $backendUserDefaultFolder->getSubfolder($personalDictionaryFolderName);
449 } else {
450 $personalDictionaryFolder = $backendUserDefaultFolder->createFolder($personalDictionaryFolderName);
451 }
452 }
453 $this->personalDictionaryPath = PATH_site . rtrim($personalDictionaryFolder->getPublicUrl(), '/');
454 }
455 }
456 return $this->personalDictionaryPath;
457 }
458
459 /**
460 * Ensures that the personal dictionary is utf-8 encoded
461 *
462 * @return void
463 */
464 protected function fixPersonalDictionaryCharacterSet() {
465 if ($this->personalDictionaryPath) {
466 // Fix the options of the personl word list and of the replacement pairs files
467 // Aspell creates such files only for the main dictionary
468 $fileNames = array();
469 $mainDictionary = preg_split('/[-_]/', $this->dictionary, 2);
470 $fileNames[0] = $this->personalDictionaryPath . '/' . '.aspell.' . $mainDictionary[0] . '.pws';
471 $fileNames[1] = $this->personalDictionaryPath . '/' . '.aspell.' . $mainDictionary[0] . '.prepl';
472 foreach ($fileNames as $fileName) {
473 if (file_exists($fileName)) {
474 $fileContent = file_get_contents($fileName);
475 if ($fileContent === FALSE) {
476 GeneralUtility::sysLog('SpellChecker personal word list read error: ' . $fileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
477 } else {
478 $fileContent = explode(LF, $fileContent);
479 if (strpos($fileContent[0], 'utf-8') === FALSE) {
480 $fileContent[0] .= ' utf-8';
481 $fileContent = implode(LF, $fileContent);
482 $result = file_put_contents($fileName, $fileContent);
483 if ($result === FALSE) {
484 GeneralUtility::sysLog('SpellChecker personal word list write error: ' . $fileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
485 }
486 }
487 }
488 }
489 }
490 }
491 }
492
493 /**
494 * Handler for the opening of a tag
495 */
496 public function startHandler($xml_parser, $tag, $attributes) {
497 if ((string)$this->xmlCharacterData !== '') {
498 $this->spellCheckHandler($xml_parser, $this->xmlCharacterData);
499 $this->xmlCharacterData = '';
500 }
501 switch ($tag) {
502 case 'spellchecker':
503 break;
504 case 'br':
505
506 case 'BR':
507
508 case 'img':
509
510 case 'IMG':
511
512 case 'hr':
513
514 case 'HR':
515
516 case 'area':
517
518 case 'AREA':
519 $this->text .= '<' . $this->csConvObj->conv_case($this->parserCharset, $tag, 'toLower') . ' ';
520 foreach ($attributes as $key => $val) {
521 $this->text .= $key . '="' . $val . '" ';
522 }
523 $this->text .= ' />';
524 break;
525 default:
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 }
532 }
533
534 /**
535 * Handler for the closing of a tag
536 */
537 public function endHandler($xml_parser, $tag) {
538 if ((string)$this->xmlCharacterData !== '') {
539 $this->spellCheckHandler($xml_parser, $this->xmlCharacterData);
540 $this->xmlCharacterData = '';
541 }
542 switch ($tag) {
543 case 'spellchecker':
544
545 case 'br':
546
547 case 'BR':
548
549 case 'img':
550
551 case 'IMG':
552
553 case 'hr':
554
555 case 'HR':
556
557 case 'input':
558
559 case 'INPUT':
560
561 case 'area':
562
563 case 'AREA':
564 break;
565 default:
566 $this->text .= '</' . $tag . '>';
567 }
568 }
569
570 /**
571 * Handler for the content of a tag
572 */
573 public function spellCheckHandler($xml_parser, $string) {
574 $incurrent = array();
575 $stringText = $string;
576 $words = preg_split($this->parserCharset == 'utf-8' ? '/\\P{L}+/u' : '/\\W+/', $stringText);
577 foreach ($words as $word) {
578 $word = preg_replace('/ /' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '', $word);
579 if ($word && !is_numeric($word)) {
580 if ($this->pspell_is_available && !$this->forceCommandMode) {
581 if (!pspell_check($this->pspell_link, $word)) {
582 if (!in_array($word, $this->misspelled)) {
583 if (sizeof($this->misspelled) != 0) {
584 $this->suggestedWords .= ',';
585 }
586 $suggest = array();
587 $suggest = pspell_suggest($this->pspell_link, $word);
588 if (sizeof($suggest) != 0) {
589 $this->suggestionCount++;
590 $this->suggestedWordCount += sizeof($suggest);
591 }
592 $this->suggestedWords .= '"' . $word . '":"' . implode(',', $suggest) . '"';
593 $this->misspelled[] = $word;
594 unset($suggest);
595 }
596 if (!in_array($word, $incurrent)) {
597 $stringText = preg_replace('/\\b' . $word . '\\b/' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '<span class="htmlarea-spellcheck-error">' . $word . '</span>', $stringText);
598 $incurrent[] = $word;
599 }
600 }
601 } else {
602 $tmpFileName = GeneralUtility::tempnam($this->filePrefix);
603 if (!($filehandle = fopen($tmpFileName, 'wb'))) {
604 echo 'SpellChecker tempfile open error';
605 }
606 if (!fwrite($filehandle, $word)) {
607 echo 'SpellChecker tempfile write error';
608 }
609 if (!fclose($filehandle)) {
610 echo 'SpellChecker tempfile close error';
611 }
612 $catCommand = TYPO3_OS === 'WIN' ? 'type' : 'cat';
613 $AspellCommand = $catCommand . ' ' . escapeshellarg($tmpFileName) . ' | '
614 . $this->AspellDirectory
615 . ' -a check'
616 . ' --mode=none'
617 . ' --sug-mode=' . escapeshellarg($this->pspellMode)
618 . ($this->personalDictionaryPath ? ' --home-dir=' . escapeshellarg($this->personalDictionaryPath) : '')
619 . ' --lang=' . escapeshellarg($this->dictionary)
620 . ' --encoding=' . escapeshellarg($this->aspellEncoding)
621 . ' 2>&1';
622 $AspellAnswer = shell_exec($AspellCommand);
623 $AspellResultLines = array();
624 $AspellResultLines = GeneralUtility::trimExplode(LF, $AspellAnswer, TRUE);
625 if (substr($AspellResultLines[0], 0, 6) == 'Error:') {
626 echo '{' . $AspellAnswer . '}';
627 }
628 GeneralUtility::unlink_tempfile($tmpFileName);
629 if ($AspellResultLines['1'][0] !== '*') {
630 if (!in_array($word, $this->misspelled)) {
631 if (sizeof($this->misspelled) != 0) {
632 $this->suggestedWords .= ',';
633 }
634 $suggest = array();
635 $suggestions = array();
636 if ($AspellResultLines['1'][0] === '&') {
637 $suggestions = GeneralUtility::trimExplode(':', $AspellResultLines['1'], TRUE);
638 $suggest = GeneralUtility::trimExplode(',', $suggestions['1'], TRUE);
639 }
640 if (sizeof($suggest) != 0) {
641 $this->suggestionCount++;
642 $this->suggestedWordCount += sizeof($suggest);
643 }
644 $this->suggestedWords .= '"' . $word . '":"' . implode(',', $suggest) . '"';
645 $this->misspelled[] = $word;
646 unset($suggest);
647 unset($suggestions);
648 }
649 if (!in_array($word, $incurrent)) {
650 $stringText = preg_replace('/\\b' . $word . '\\b/' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '<span class="htmlarea-spellcheck-error">' . $word . '</span>', $stringText);
651 $incurrent[] = $word;
652 }
653 }
654 unset($AspellResultLines);
655 }
656 $this->wordCount++;
657 }
658 }
659 $this->text .= $stringText;
660 unset($incurrent);
661 }
662
663 /**
664 * Handler for collecting data within a tag
665 */
666 public function collectDataHandler($xml_parser, $string) {
667 $this->xmlCharacterData .= $string;
668 }
669
670 /**
671 * Default handler for the xml parser
672 */
673 public function defaultHandler($xml_parser, $string) {
674 $this->text .= $string;
675 }
676
677 }