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