* Added "rtehtmlarea" as a system extension
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / pi1 / class.tx_rtehtmlarea_pi1.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2003-2005 Stanislas Rolland (stanislas.rolland@fructifor.ca)
6 * All rights reserved
7 *
8 * This script is part of the Typo3 project. The Typo3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24 /**
25 * Spell checking plugin 'tx_rtehtmlarea_pi1' for the htmlArea RTE extension.
26 *
27 * @author Stanislas Rolland <stanislas.rolland@fructifor.ca>
28 *
29 */
30 require_once(PATH_tslib.'class.tslib_pibase.php');
31
32 class tx_rtehtmlarea_pi1 extends tslib_pibase {
33 var $cObj; // The backReference to the mother cObj object set at call time
34 var $prefixId = 'tx_rtehtmlarea_pi1'; // Same as class name
35 var $scriptRelPath = 'pi1/class.tx_rtehtmlarea_pi1.php'; // Path to this script relative to the extension dir.
36 var $extKey = 'rtehtmlarea'; // The extension key.
37 var $conf = array();
38 var $siteUrl;
39 var $charset = 'utf-8';
40 var $parserCharset = 'utf-8';
41 var $typoVersion; // Typo3 version
42 var $result;
43 var $text;
44 var $misspelled = array();
45 var $suggestedWords;
46 var $wordCount = 0;
47 var $suggestionCount = 0;
48 var $suggestedWordCount = 0;
49 var $pspell_link;
50 var $pspellMode = 'normal';
51 var $dictionary;
52 var $AspellDirectory;
53 var $pspell_is_available;
54 var $forceCommandMode = 0;
55 var $filePrefix = 'rtehtmlarea_';
56 var $uploadFolder = 'uploads/tx_rtehtmlarea/';
57 var $userUid;
58 var $personalDictsArg = '';
59
60 /**
61 * Main class of Spell Checker plugin for Typo3 CMS
62 *
63 * @param string $content: content to be displayed
64 * @param array $conf: TS setup for the plugin
65 * @return string content produced by the plugin
66 */
67 function main($conf) {
68 $this->conf = $conf;
69 $this->tslib_pibase();
70 $this->pi_setPiVarDefaults();
71 $this->pi_loadLL();
72 $this->pi_USER_INT_obj = 1; // Disable caching
73 // Setting start time
74 $time_start = microtime(true);
75 $this->pspell_is_available = in_array('pspell', get_loaded_extensions());
76 $this->AspellDirectory = trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['AspellDirectory'])? trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['AspellDirectory']) : '/usr/bin/aspell';
77 $this->forceCommandMode = (trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['forceCommandMode']))? trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['forceCommandMode']) : 0;
78 $safe_mode_is_enabled = ini_get('safe_mode');
79 if($safe_mode_is_enabled && !$this->pspell_is_available ) echo('Configuration problem: Spell checking cannot be performed');
80 if($safe_mode_is_enabled && $this->forceCommandMode) echo('Configuration problem: Spell checking cannot be performed in command mode');
81 if(!$safe_mode_is_enabled && (!$this->pspell_is_available || $this->forceCommandMode)) {
82 $AspellVersionString = explode('Aspell', shell_exec( $this->AspellDirectory.' -v'));
83 $AspellVersion = substr( $AspellVersionString[1], 0, 4);
84 if( doubleval($AspellVersion) < doubleval('0.5') && (!$this->pspell_is_available || $this->forceCommandMode)) echo('Configuration problem: Aspell version ' . $AspellVersion . ' too old. Spell checking cannot be performed in command mode');
85 }
86
87 // Setting the list of dictionaries
88 if(!$safe_mode_is_enabled && (!$this->pspell_is_available || $this->forceCommandMode)) {
89 $dictionaryList = shell_exec( $this->AspellDirectory.' dump dicts');
90 $dictionaryList = implode(',', t3lib_div::trimExplode(chr(10), $dictionaryList, 1));
91 }
92 if( empty($dictionaryList) ) {
93 $dictionaryList = trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['dictionaryList']);
94 }
95 if( empty($dictionaryList) ) {
96 $dictionaryList = 'en';
97 }
98 $dictionaryArray = t3lib_div::trimExplode(',', $dictionaryList, 1);
99
100 $defaultDictionary = trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['defaultDictionary']);
101 if(!$defaultDictionary || !in_array($defaultDictionary, $dictionaryArray)) {
102 $defaultDictionary = 'en';
103 }
104
105 // Get the defined sys_language codes
106 $languageArray = array();
107 $tableA = 'sys_language';
108 $tableB = 'static_languages';
109 $selectFields = $tableA . '.uid,' . $tableB . '.lg_iso_2,' . $tableB . '.lg_country_iso_2';
110 $table = $tableA . ' LEFT JOIN ' . $tableB . ' ON ' . $tableA . '.static_lang_isocode=' . $tableB . '.uid';
111 $whereClause = '1=1 ';
112 $whereClause .= ' AND ' . $tableA . '.hidden != "1"';
113 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($selectFields, $table, $whereClause);
114 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
115 $languageArray[] = strtolower($row['lg_iso_2']).($row['lg_country_iso_2']?'_'.$row['lg_country_iso_2']:'');
116 }
117 if(!in_array($defaultDictionary, $languageArray)) {
118 $languageArray[] = $defaultDictionary;
119 }
120 foreach ($dictionaryArray as $key => $dict) {
121 $lang = explode('-', $dict);
122 if( !in_array(substr($dict, 0, 2), $languageArray) || !empty($lang[1])) {
123 unset($dictionaryArray[$key]);
124 } else {
125 $dictionaryArray[$key] = $lang[0];
126 }
127 }
128 uasort($dictionaryArray, 'strcoll');
129 $dictionaryList = implode(',', $dictionaryArray);
130
131 // Setting the dictionary
132 $this->dictionary = t3lib_div::_POST('dictionary');
133 if( empty($this->dictionary) || !in_array($this->dictionary, $dictionaryArray)) {
134 $this->dictionary = $defaultDictionary;
135 }
136 $dictionaries = substr_replace($dictionaryList, '@'.$this->dictionary, strpos($dictionaryList, $this->dictionary), strlen($this->dictionary));
137
138 //$locale = setlocale(LC_ALL, $this->dictionary);
139
140 // Setting the pspell suggestion mode
141 $this->pspellMode = t3lib_div::_POST('pspell_mode')?t3lib_div::_POST('pspell_mode'): $this->pspellMode;
142 switch($this->pspellMode) {
143 case 'ultra':
144 case 'fast':
145 $pspellModeFlag = PSPELL_FAST;
146 break;
147 case 'bad-spellers':
148 $pspellModeFlag = PSPELL_BAD_SPELLERS;
149 break;
150 case 'normal':
151 default:
152 $pspellModeFlag = PSPELL_NORMAL;
153 break;
154 }
155
156 // Setting the charset
157 if( t3lib_div::_POST('pspell_charset') ) $this->charset = trim(t3lib_div::_POST('pspell_charset'));
158 if(strtolower($this->charset) == 'iso-8859-1') $this->parserCharset = strtolower($this->charset);
159 $internal_encoding = mb_internal_encoding(strtoupper($this->parserCharset));
160 //$regex_encoding = mb_regex_encoding (strtoupper($this->parserCharset));
161 // However, we are going to work only in the parser charset
162 if($this->pspell_is_available && !$this->forceCommandMode) {
163 $this->pspell_link = pspell_new($this->dictionary, '', '', $this->parserCharset, $pspellModeFlag);
164 }
165
166 // Setting the path to user personal dicts, if any
167 if (t3lib_div::_POST('enablePersonalDicts') == 'true') {
168 $this->userUid = t3lib_div::_POST('userUid');
169 if ($this->userUid) {
170 $this->personalDictPath = t3lib_div::getFileAbsFileName($this->uploadFolder . $this->userUid);
171 if (!is_dir($this->personalDictPath)) {
172 t3lib_div::mkdir($this->personalDictPath);
173 }
174 $this->personalDictsArg = ' --home-dir=' . $this->personalDictPath;
175 }
176 }
177
178 $cmd = t3lib_div::_POST('cmd');
179 if ($cmd == 'learn' && !$safe_mode_is_enabled) {
180 // Updating the personal word list
181 $to_p_dict = t3lib_div::_POST('to_p_dict');
182 $to_p_dict = $to_p_dict ? $to_p_dict : array();
183 $to_r_list = t3lib_div::_POST('to_r_list');
184 $to_r_list = $to_r_list ? $to_r_list : array();
185 header('Content-Type: text/plain; charset=' . strtoupper($this->parserCharset));
186 header('Pragma: no-cache');
187 //print_r($to_r_list);
188 if($to_p_dict || $to_r_list) {
189 $tmpFileName = t3lib_div::tempnam($this->filePrefix);
190 if($filehandle = fopen($tmpFileName,'wb')) {
191 foreach ($to_p_dict as $personal_word) {
192 $cmd = '&' . $personal_word . "\n";
193 echo $cmd;
194 fwrite($filehandle, $cmd, strlen($cmd));
195 }
196 foreach ($to_r_list as $replace_pair) {
197 $cmd = '$$ra ' . $replace_pair[0] . ' , ' . $replace_pair[1] . "\n";
198 echo $cmd;
199 fwrite($filehandle, $cmd, strlen($cmd));
200 }
201 $cmd = "#\n";
202 echo $cmd;
203 fwrite($filehandle, $cmd, strlen($cmd));
204 fclose($filehandle);
205 $AspellCommand = 'cat ' . $tmpFileName . ' | ' . $this->AspellDirectory . ' -a --mode=none' . $this->personalDictsArg . ' --lang=' .$this->dictionary . ' --encoding=' . $this->parserCharset . ' 2>&1';
206 print $AspellCommand . "\n";
207 print shell_exec($AspellCommand);
208 t3lib_div::unlink_tempfile($tmpFileName);
209 echo('Personal word list was updated.');
210 } else {
211 echo('SpellChecker tempfile open error.');
212 }
213 } else {
214 echo('Nothing to add to the personal word list.');
215 }
216 flush();
217 exit();
218 } else {
219 // Check spelling content
220 // Initialize output
221 $this->result = '<?xml version="1.0" encoding="' . $this->parserCharset . '"?>
222 <!DOCTYPE html
223 PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
224 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
225 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . substr($this->dictionary, 0, 2) . '" lang="' . substr($this->dictionary, 0, 2) . '">
226 <html>
227 <head>
228 <meta http-equiv="Content-Type" content="text/html; charset=' . $this->parserCharset . '" />
229 <link rel="stylesheet" type="text/css" media="all" href="spell-check-style.css" />
230 <script type="text/javascript">
231 /*<![CDATA[*/
232 ';
233
234 // Getting the input content
235 $content = t3lib_div::_POST('content');
236
237 // Parsing the input HTML
238 $parser = xml_parser_create(strtoupper($this->parserCharset));
239 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
240 xml_set_object($parser, &$this);
241 if( !xml_set_element_handler( $parser, 'startHandler', 'endHandler')) echo('Bad xml handler setting');
242 if( !xml_set_character_data_handler ( $parser, 'spellCheckHandler')) echo('Bad xml handler setting');
243 if( !xml_set_default_handler( $parser, 'defaultHandler')) echo('Bad xml handler setting');
244 if(! xml_parse($parser,'<?xml version="1.0" encoding="' . $this->parserCharset . '"?><SPELLCHECKER> ' . mb_ereg_replace('&nbsp;', ' ', $content) . ' </SPELLCHECKER>')) echo('Bad parsing');
245 if( xml_get_error_code($parser)) {
246 die('Line '.xml_get_current_line_number($parser).': '.xml_error_string(xml_get_error_code($parser)));
247 }
248 xml_parser_free($parser);
249 if($this->pspell_is_available && !$this->forceCommandMode) {
250 pspell_clear_session ($this->pspell_link);
251 }
252 $this->result .= 'var suggested_words = {' . $this->suggestedWords . '};
253 ';
254
255 // Calculating parsing and spell checkting time
256 $time = number_format(microtime(true) - $time_start, 2, ',', ' ');
257
258 // Insert spellcheck info
259 $this->result .= 'var spellcheck_info = { "Total words":"'.$this->wordCount.'","Misspelled words":"'.sizeof($this->misspelled).'","Total suggestions":"'.$this->suggestionCount.'","Total words suggested":"'.$this->suggestedWordCount.'","Spelling checked in":"'.$time.'" };
260 /*]]>*/
261 </script>
262 </head>
263 ';
264 $this->result .= '<body onload="window.parent.finishedSpellChecking();">';
265 $this->result .= $this->text;
266 $this->result .= '<div id="HA-spellcheck-dictionaries">'.$dictionaries.'</div>';
267
268 // Closing
269 $this->result .= '
270 </body></html>';
271
272 // Outputting
273 echo $this->result;
274 }
275
276 } // end of function main
277
278 function startHandler($xml_parser, $tag, $attributes) {
279 switch($tag) {
280 case 'SPELLCHECKER':
281 break;
282 case 'br':
283 case 'BR':
284 case 'img':
285 case 'IMG':
286 case 'hr':
287 case 'HR':
288 case 'area':
289 case 'AREA':
290 $this->text .= '<'. mb_strtolower($tag) . ' ';
291 foreach( $attributes as $key => $val) {
292 $this->text .= $key . '="' . $val . '" ';
293 }
294 $this->text .= ' />';
295 break;
296 default:
297 $this->text .= '<'. mb_strtolower($tag) . ' ';
298 foreach( $attributes as $key => $val) {
299 $this->text .= $key . '="' . $val . '" ';
300 }
301 $this->text .= '>';
302 break;
303 }
304 return;
305 }
306
307 function endHandler($xml_parser, $tag) {
308 switch($tag) {
309 case 'SPELLCHECKER':
310 break;
311 case 'br':
312 case 'BR':
313 case 'img':
314 case 'IMG':
315 case 'hr':
316 case 'HR':
317 case 'input':
318 case 'INPUT':
319 case 'area':
320 case 'AREA':
321 break;
322 default:
323 $this->text .= '</' . $tag . '>';
324 break;
325 }
326 return;
327 }
328
329 function spellCheckHandler($xml_parser, $string) {
330 $incurrent=array();
331 $stringText = $string;
332 $words = mb_split('\W+', $stringText);
333 while( list(,$word) = each($words) ) {
334 $word = mb_ereg_replace(' ', '', $word);
335 if( $word && !is_numeric($word)) {
336 if($this->pspell_is_available && !$this->forceCommandMode) {
337 if (!pspell_check($this->pspell_link, $word)) {
338 if(!in_array($word, $this->misspelled)) {
339 if(sizeof($this->misspelled) != 0 ) {
340 $this->suggestedWords .= ',';
341 }
342 $suggest = array();
343 $suggest = pspell_suggest($this->pspell_link, $word);
344 if(sizeof($suggest) != 0 ) {
345 $this->suggestionCount++;
346 $this->suggestedWordCount += sizeof($suggest);
347 }
348 $this->suggestedWords .= '"'.$word.'":"'.implode(',',$suggest).'"';
349 $this->misspelled[] = $word;
350 unset($suggest);
351 }
352 if( !in_array($word, $incurrent) ) {
353 $stringText = mb_ereg_replace('\b'.$word.'\b', '<span class="HA-spellcheck-error">'.$word.'</span>', $stringText);
354 $incurrent[] = $word;
355 }
356 }
357 } else {
358 $tmpFileName = t3lib_div::tempnam($this->filePrefix);
359 if(!$filehandle = fopen($tmpFileName,'wb')) echo('SpellChecker tempfile open error');
360 if(!fwrite($filehandle, $word)) echo('SpellChecker tempfile write error');
361 if(!fclose($filehandle)) echo('SpellChecker tempfile close error');
362 $AspellCommand = 'cat ' . $tmpFileName . ' | ' . $this->AspellDirectory . ' -a check --mode=none --sug-mode=' . $this->pspellMode . $this->personalDictsArg . ' --lang=' .$this->dictionary . ' --encoding=' . $this->parserCharset . ' 2>&1';
363 $AspellAnswer = shell_exec($AspellCommand);
364 $AspellResultLines = array();
365 $AspellResultLines = t3lib_div::trimExplode(chr(10), $AspellAnswer, 1);
366 if(substr($AspellResultLines[0],0,6) == 'Error:') echo("{$AspellAnswer}");
367 t3lib_div::unlink_tempfile($tmpFileName);
368 if(substr($AspellResultLines['1'],0,1) != '*') {
369 if(!in_array($word, $this->misspelled)) {
370 if(sizeof($this->misspelled) != 0 ) {
371 $this->suggestedWords .= ',';
372 }
373 $suggest = array();
374 $suggestions = array();
375 if (substr($AspellResultLines['1'],0,1) == '&') {
376 $suggestions = t3lib_div::trimExplode(':', $AspellResultLines['1'], 1);
377 $suggest = t3lib_div::trimExplode(',', $suggestions['1'], 1);
378 }
379 if (sizeof($suggest) != 0) {
380 $this->suggestionCount++;
381 $this->suggestedWordCount += sizeof($suggest);
382 }
383 $this->suggestedWords .= '"'.$word.'":"'.implode(',',$suggest).'"';
384 $this->misspelled[] = $word;
385 unset($suggest);
386 unset($suggestions);
387 }
388 if (!in_array($word, $incurrent)) {
389 $stringText = mb_ereg_replace('\b'.$word.'\b', '<span class="HA-spellcheck-error">'.$word.'</span>', $stringText);
390 $incurrent[] = $word;
391 }
392 }
393 unset($AspellResultLines);
394 }
395 $this->wordCount++;
396 }
397 }
398 $this->text .= $stringText;
399 unset($incurrent);
400 return;
401 }
402
403 function defaultHandler($xml_parser, $string) {
404 $this->text .= $string;
405 return;
406 }
407
408 } // end of class
409
410 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/rtehtmlarea/pi1/class.tx_rtehtmlarea_pi1.php']) {
411 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/rtehtmlarea/pi1/class.tx_rtehtmlarea_pi1.php']);
412 }
413
414 ?>