TYPO3 API  SVNRelease
class.tx_rtehtmlarea_pi1.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2003-2011 Stanislas Rolland <typo3(arobas)sjbr.ca>
00006 *  All rights reserved
00007 *
00008 *  This script is part of the Typo3 project. The Typo3 project is
00009 *  free software; you can redistribute it and/or modify
00010 *  it under the terms of the GNU General Public License as published by
00011 *  the Free Software Foundation; either version 2 of the License, or
00012 *  (at your option) any later version.
00013 *
00014 *  The GNU General Public License can be found at
00015 *  http://www.gnu.org/copyleft/gpl.html.
00016 *
00017 *  This script is distributed in the hope that it will be useful,
00018 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020 *  GNU General Public License for more details.
00021 *
00022 *  This copyright notice MUST APPEAR in all copies of the script!
00023 ***************************************************************/
00024 /**
00025  * Spell checking plugin 'tx_rtehtmlarea_pi1' for the htmlArea RTE extension.
00026  *
00027  * @author Stanislas Rolland <typo3(arobas)sjbr.ca>
00028  *
00029  * TYPO3 SVN ID: $Id: class.tx_rtehtmlarea_pi1.php 10450 2011-02-12 00:00:20Z stan $
00030  *
00031  */
00032 
00033 class tx_rtehtmlarea_pi1 {
00034 
00035     protected $csConvObj;
00036     var $extKey = 'rtehtmlarea'; // The extension key.
00037     var $siteUrl;
00038     var $charset = 'utf-8';
00039     var $parserCharset = 'utf-8';
00040     var $defaultAspellEncoding = 'utf-8';
00041     var $aspellEncoding;
00042     var $result;
00043     var $text;
00044     var $misspelled = array();
00045     var $suggestedWords;
00046     var $wordCount = 0;
00047     var $suggestionCount = 0;
00048     var $suggestedWordCount = 0;
00049     var $pspell_link;
00050     var $pspellMode = 'normal';
00051     var $dictionary;
00052     var $AspellDirectory;
00053     var $pspell_is_available;
00054     var $forceCommandMode = 0;
00055     var $filePrefix = 'rtehtmlarea_';
00056     var $uploadFolder = 'uploads/tx_rtehtmlarea/';
00057     var $userUid;
00058     var $personalDictsArg = '';
00059     var $xmlCharacterData = '';
00060 
00061     /**
00062      * Main class of Spell Checker plugin for Typo3 CMS
00063      *
00064      * @return  string      content produced by the plugin
00065      */
00066     function main() {
00067 
00068         $this->csConvObj = t3lib_div::makeInstance('t3lib_cs');
00069 
00070             // Setting start time
00071         $time_start = microtime(true);
00072         $this->pspell_is_available = in_array('pspell', get_loaded_extensions());
00073         $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';
00074         $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;
00075         $safe_mode_is_enabled = t3lib_utility_PhpOptions::isSafeModeEnabled();
00076         if($safe_mode_is_enabled && !$this->pspell_is_available ) echo('Configuration problem: Spell checking cannot be performed');
00077         if($safe_mode_is_enabled && $this->forceCommandMode) echo('Configuration problem: Spell checking cannot be performed in command mode');
00078         if(!$safe_mode_is_enabled && (!$this->pspell_is_available || $this->forceCommandMode)) {
00079             $AspellVersionString = explode('Aspell', shell_exec( $this->AspellDirectory.' -v'));
00080             $AspellVersion = substr( $AspellVersionString[1], 0, 4);
00081             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');
00082             $this->defaultAspellEncoding = trim(shell_exec($this->AspellDirectory.' config encoding'));
00083         }
00084 
00085             // Setting the list of dictionaries
00086         if (!$safe_mode_is_enabled && (!$this->pspell_is_available || $this->forceCommandMode)) {
00087             $dictionaryList = shell_exec( $this->AspellDirectory.' dump dicts');
00088             $dictionaryList = implode(',', t3lib_div::trimExplode(LF, $dictionaryList, 1));
00089         }
00090         if (empty($dictionaryList)) {
00091             $dictionaryList = t3lib_div::_POST('showDictionaries');
00092         }
00093         $dictionaryArray = t3lib_div::trimExplode(',', $dictionaryList, 1);
00094         $restrictToDictionaries = t3lib_div::_POST('restrictToDictionaries');
00095         if ($restrictToDictionaries) {
00096             $dictionaryArray = array_intersect($dictionaryArray, t3lib_div::trimExplode(',', $restrictToDictionaries, 1));
00097         }
00098         if (!count($dictionaryArray)) {
00099             $dictionaryArray[] = 'en';
00100         }
00101         $this->dictionary = t3lib_div::_POST('dictionary');
00102         $defaultDictionary = $this->dictionary;
00103         if (!$defaultDictionary || !in_array($defaultDictionary, $dictionaryArray)) {
00104             $defaultDictionary = 'en';
00105         }
00106         uasort($dictionaryArray, 'strcoll');
00107         $dictionaryList = implode(',', $dictionaryArray);
00108             // Setting the dictionary
00109         if (empty($this->dictionary) || !in_array($this->dictionary, $dictionaryArray)) {
00110             $this->dictionary = 'en';
00111         }
00112             // Setting the pspell suggestion mode
00113         $this->pspellMode = t3lib_div::_POST('pspell_mode')?t3lib_div::_POST('pspell_mode'): $this->pspellMode;
00114             // Now sanitize $this->pspellMode
00115         $this->pspellMode = t3lib_div::inList('ultra,fast,normal,bad-spellers',$this->pspellMode)?$this->pspellMode:'normal';
00116         switch($this->pspellMode) {
00117             case 'ultra':
00118             case 'fast':
00119                 $pspellModeFlag = PSPELL_FAST;
00120                 break;
00121             case 'bad-spellers':
00122                 $pspellModeFlag = PSPELL_BAD_SPELLERS;
00123                 break;
00124             case 'normal':
00125             default:
00126                 $pspellModeFlag = PSPELL_NORMAL;
00127                 break;
00128         }
00129 
00130             // Setting the charset
00131         if (t3lib_div::_POST('pspell_charset')) {
00132             $this->charset = trim(t3lib_div::_POST('pspell_charset'));
00133         }
00134         if (strtolower($this->charset) == 'iso-8859-1') {
00135             $this->parserCharset = strtolower($this->charset);
00136         }
00137 
00138             // In some configurations, Aspell uses 'iso8859-1' instead of 'iso-8859-1'
00139         $this->aspellEncoding = $this->parserCharset;
00140         if ($this->parserCharset == 'iso-8859-1' && strstr($this->defaultAspellEncoding, '8859-1')) {
00141             $this->aspellEncoding = $this->defaultAspellEncoding;
00142         }
00143 
00144             // However, we are going to work only in the parser charset
00145         if($this->pspell_is_available && !$this->forceCommandMode) {
00146             $this->pspell_link = pspell_new($this->dictionary, '', '', $this->parserCharset, $pspellModeFlag);
00147         }
00148 
00149             // Setting the path to user personal dicts, if any
00150         if (t3lib_div::_POST('enablePersonalDicts') == 'true' && TYPO3_MODE == 'BE' && is_object($GLOBALS['BE_USER'])) {
00151             $this->userUid = 'BE_' . $GLOBALS['BE_USER']->user['uid'];
00152             if ($this->userUid) {
00153                 $this->personalDictPath = t3lib_div::getFileAbsFileName($this->uploadFolder . $this->userUid);
00154                 if (!is_dir($this->personalDictPath)) {
00155                     t3lib_div::mkdir($this->personalDictPath);
00156                 }
00157                     // escape here for later use
00158                 $this->personalDictsArg = ' --home-dir=' . escapeshellarg($this->personalDictPath);
00159             }
00160         }
00161 
00162         $cmd = t3lib_div::_POST('cmd');
00163         if ($cmd == 'learn' && !$safe_mode_is_enabled) {
00164                 // Only availble for BE_USERS, die silently if someone has gotten here by accident
00165             if (TYPO3_MODE !='BE' || !is_object($GLOBALS['BE_USER'])) die('');
00166                 // Updating the personal word list
00167             $to_p_dict = t3lib_div::_POST('to_p_dict');
00168             $to_p_dict = $to_p_dict ? $to_p_dict : array();
00169             $to_r_list = t3lib_div::_POST('to_r_list');
00170             $to_r_list = $to_r_list ? $to_r_list : array();
00171             header('Content-Type: text/plain; charset=' . strtoupper($this->parserCharset));
00172             header('Pragma: no-cache');
00173             if($to_p_dict || $to_r_list) {
00174                 $tmpFileName = t3lib_div::tempnam($this->filePrefix);
00175                 if($filehandle = fopen($tmpFileName,'wb')) {
00176                     foreach ($to_p_dict as $personal_word) {
00177                         $cmd = '&' . $personal_word . LF;
00178                         echo $cmd;
00179                         fwrite($filehandle, $cmd, strlen($cmd));
00180                     }
00181                     foreach ($to_r_list as $replace_pair) {
00182                         $cmd = '$$ra ' . $replace_pair[0] . ' , ' . $replace_pair[1] . LF;
00183                         echo $cmd;
00184                         fwrite($filehandle, $cmd, strlen($cmd));
00185                     }
00186                     $cmd = "#\n";
00187                     echo $cmd;
00188                     fwrite($filehandle, $cmd, strlen($cmd));
00189                     fclose($filehandle);
00190                         // $this->personalDictsArg has already been escapeshellarg()'ed above, it is an optional paramter and might be empty here
00191                     $AspellCommand = 'cat ' . escapeshellarg($tmpFileName) . ' | ' . $this->AspellDirectory . ' -a --mode=none' . $this->personalDictsArg . ' --lang=' . escapeshellarg($this->dictionary) . ' --encoding=' . escapeshellarg($this->aspellEncoding) . ' 2>&1';
00192                     print $AspellCommand . LF;
00193                     print shell_exec($AspellCommand);
00194                     t3lib_div::unlink_tempfile($tmpFileName);
00195                     echo('Personal word list was updated.');
00196                 } else {
00197                     echo('SpellChecker tempfile open error.');
00198                 }
00199             } else {
00200                 echo('Nothing to add to the personal word list.');
00201             }
00202             flush();
00203             exit();
00204         } else {
00205                 // Check spelling content
00206                 // Initialize output
00207             $this->result = '<?xml version="1.0" encoding="' . $this->parserCharset . '"?>
00208 <!DOCTYPE html
00209      PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
00210      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
00211 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . substr($this->dictionary, 0, 2) . '" lang="' . substr($this->dictionary, 0, 2) . '">
00212 <head>
00213 <meta http-equiv="Content-Type" content="text/html; charset=' . $this->parserCharset . '" />
00214 <link rel="stylesheet" type="text/css" media="all" href="' . (TYPO3_MODE == 'BE' ? '../' : '') . t3lib_extMgm::siteRelPath($this->extKey) . '/htmlarea/plugins/SpellChecker/spell-check-style.css" />
00215 <script type="text/javascript">
00216 /*<![CDATA[*/
00217 <!--
00218 ';
00219 
00220                 // Getting the input content
00221             $content = t3lib_div::_POST('content');
00222 
00223                 // Parsing the input HTML
00224             $parser = xml_parser_create(strtoupper($this->parserCharset));
00225             xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
00226             xml_set_object($parser, $this);
00227             if (!xml_set_element_handler($parser, 'startHandler', 'endHandler')) echo('Bad xml handler setting');
00228             if (!xml_set_character_data_handler($parser, 'collectDataHandler')) echo('Bad xml handler setting');
00229             if (!xml_set_default_handler($parser, 'defaultHandler')) echo('Bad xml handler setting');
00230             if (!xml_parse($parser,'<?xml version="1.0" encoding="' . $this->parserCharset . '"?><spellchecker> ' . preg_replace('/&nbsp;/'.(($this->parserCharset == 'utf-8')?'u':''), ' ', $content) . ' </spellchecker>')) echo('Bad parsing');
00231             if (xml_get_error_code($parser)) {
00232                 die('Line '.xml_get_current_line_number($parser).': '.xml_error_string(xml_get_error_code($parser)));
00233             }
00234             xml_parser_free($parser);
00235             if ($this->pspell_is_available && !$this->forceCommandMode) {
00236                 pspell_clear_session ($this->pspell_link);
00237             }
00238             $this->result .= 'var suggestedWords = {' . $this->suggestedWords . '};
00239 var dictionaries = "' . $dictionaryList . '";
00240 var selectedDictionary = "' . $this->dictionary . '";
00241 ';
00242 
00243                 // Calculating parsing and spell checkting time
00244             $time = number_format(microtime(true) - $time_start, 2, ',', ' ');
00245 
00246                 // Insert spellcheck info
00247             $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.'" };
00248 // -->
00249 /*]]>*/
00250 </script>
00251 </head>
00252 ';
00253             $this->result .= '<body onload="window.parent.RTEarea[\'' . t3lib_div::_POST('editorId') . '\'].editor.getPlugin(\'SpellChecker\').spellCheckComplete();">';
00254             $this->result .= preg_replace('/'.preg_quote('<?xml').'.*'.preg_quote('?>').'['.preg_quote(LF.CR.chr(32)).']*/'.(($this->parserCharset == 'utf-8')?'u':''), '', $this->text);
00255             $this->result .= '<div style="display: none;">'.$dictionaries.'</div>';
00256 
00257                 // Closing
00258             $this->result .= '
00259 </body></html>';
00260 
00261                 // Outputting
00262             header('Content-Type: text/html; charset=' . strtoupper($this->parserCharset));
00263             echo $this->result;
00264         }
00265 
00266     }  // end of function main
00267 
00268     function startHandler($xml_parser, $tag, $attributes) {
00269 
00270         if (strlen($this->xmlCharacterData)) {
00271             $this->spellCheckHandler($xml_parser, $this->xmlCharacterData);
00272             $this->xmlCharacterData = '';
00273         }
00274 
00275         switch($tag) {
00276             case 'spellchecker':
00277                 break;
00278             case 'br':
00279             case 'BR':
00280             case 'img':
00281             case 'IMG':
00282             case 'hr':
00283             case 'HR':
00284             case 'area':
00285             case 'AREA':
00286                 $this->text .= '<'. $this->csConvObj->conv_case($this->parserCharset, $tag, 'toLower') . ' ';
00287                 foreach( $attributes as $key => $val) {
00288                     $this->text .= $key . '="' . $val . '" ';
00289                 }
00290                 $this->text .= ' />';
00291                 break;
00292             default:
00293                 $this->text .= '<'. $this->csConvObj->conv_case($this->parserCharset, $tag, 'toLower') . ' ';
00294                 foreach( $attributes as $key => $val) {
00295                     $this->text .= $key . '="' . $val . '" ';
00296                 }
00297                 $this->text .= '>';
00298                 break;
00299         }
00300         return;
00301     }
00302 
00303     function endHandler($xml_parser, $tag) {
00304         if (strlen($this->xmlCharacterData)) {
00305             $this->spellCheckHandler($xml_parser, $this->xmlCharacterData);
00306             $this->xmlCharacterData = '';
00307         }
00308 
00309         switch($tag) {
00310             case 'spellchecker':
00311                 break;
00312             case 'br':
00313             case 'BR':
00314             case 'img':
00315             case 'IMG':
00316             case 'hr':
00317             case 'HR':
00318             case 'input':
00319             case 'INPUT':
00320             case 'area':
00321             case 'AREA':
00322                 break;
00323             default:
00324                 $this->text .= '</' . $tag . '>';
00325                 break;
00326         }
00327         return;
00328     }
00329 
00330     function spellCheckHandler($xml_parser, $string) {
00331         $incurrent=array();
00332         $stringText = $string;
00333         $words = preg_split((($this->parserCharset == 'utf-8')?'/\P{L}+/u':'/\W+/'), $stringText);
00334         while( list(,$word) = each($words) ) {
00335             $word = preg_replace('/ /'.(($this->parserCharset == 'utf-8')?'u':''), '', $word);
00336             if( $word && !is_numeric($word)) {
00337                 if($this->pspell_is_available && !$this->forceCommandMode) {
00338                     if (!pspell_check($this->pspell_link, $word)) {
00339                         if(!in_array($word, $this->misspelled)) {
00340                             if(sizeof($this->misspelled) != 0 ) {
00341                                 $this->suggestedWords .= ',';
00342                             }
00343                             $suggest = array();
00344                             $suggest = pspell_suggest($this->pspell_link, $word);
00345                             if(sizeof($suggest) != 0 ) {
00346                                 $this->suggestionCount++;
00347                                 $this->suggestedWordCount += sizeof($suggest);
00348                             }
00349                             $this->suggestedWords .= '"'.$word.'":"'.implode(',',$suggest).'"';
00350                             $this->misspelled[] = $word;
00351                             unset($suggest);
00352                         }
00353                         if( !in_array($word, $incurrent) ) {
00354                             $stringText = preg_replace('/\b'.$word.'\b/'.(($this->parserCharset == 'utf-8')?'u':''), '<span class="htmlarea-spellcheck-error">'.$word.'</span>', $stringText);
00355                             $incurrent[] = $word;
00356                         }
00357                     }
00358                 } else {
00359                     $tmpFileName = t3lib_div::tempnam($this->filePrefix);
00360                     if(!$filehandle = fopen($tmpFileName,'wb')) echo('SpellChecker tempfile open error');
00361                     if(!fwrite($filehandle, $word)) echo('SpellChecker tempfile write error');
00362                     if(!fclose($filehandle)) echo('SpellChecker tempfile close error');
00363                     $AspellCommand = 'cat ' . escapeshellarg($tmpFileName) . ' | ' . $this->AspellDirectory . ' -a check --mode=none --sug-mode=' . escapeshellarg($this->pspellMode) . $this->personalDictsArg . ' --lang=' . escapeshellarg($this->dictionary) . ' --encoding=' . escapeshellarg($this->aspellEncoding) . ' 2>&1';
00364                     $AspellAnswer = shell_exec($AspellCommand);
00365                     $AspellResultLines = array();
00366                     $AspellResultLines = t3lib_div::trimExplode(LF, $AspellAnswer, 1);
00367                     if(substr($AspellResultLines[0],0,6) == 'Error:') echo("{$AspellAnswer}");
00368                     t3lib_div::unlink_tempfile($tmpFileName);
00369                     if(substr($AspellResultLines['1'],0,1) != '*') {
00370                         if(!in_array($word, $this->misspelled)) {
00371                             if(sizeof($this->misspelled) != 0 ) {
00372                                 $this->suggestedWords .= ',';
00373                             }
00374                             $suggest = array();
00375                             $suggestions = array();
00376                             if (substr($AspellResultLines['1'],0,1) == '&') {
00377                                 $suggestions = t3lib_div::trimExplode(':', $AspellResultLines['1'], 1);
00378                                 $suggest =  t3lib_div::trimExplode(',', $suggestions['1'], 1);
00379                             }
00380                             if (sizeof($suggest) != 0) {
00381                                 $this->suggestionCount++;
00382                                 $this->suggestedWordCount += sizeof($suggest);
00383                             }
00384                             $this->suggestedWords .= '"'.$word.'":"'.implode(',',$suggest).'"';
00385                             $this->misspelled[] = $word;
00386                             unset($suggest);
00387                             unset($suggestions);
00388                         }
00389                         if (!in_array($word, $incurrent)) {
00390                             $stringText = preg_replace('/\b'.$word.'\b/'.(($this->parserCharset == 'utf-8')?'u':''), '<span class="htmlarea-spellcheck-error">'.$word.'</span>', $stringText);
00391                             $incurrent[] = $word;
00392                         }
00393                     }
00394                 unset($AspellResultLines);
00395                 }
00396                 $this->wordCount++;
00397             }
00398         }
00399         $this->text .= $stringText;
00400         unset($incurrent);
00401         return;
00402     }
00403 
00404     function collectDataHandler($xml_parser, $string) {
00405         $this->xmlCharacterData .= $string;
00406     }
00407 
00408     function defaultHandler($xml_parser, $string) {
00409         $this->text .= $string;
00410         return;
00411     }
00412 
00413 }
00414 
00415 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/rtehtmlarea/pi1/class.tx_rtehtmlarea_pi1.php'])) {
00416     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/rtehtmlarea/pi1/class.tx_rtehtmlarea_pi1.php']);
00417 }
00418 
00419 if (TYPO3_MODE=='FE') {
00420     tslib_eidtools::connectDB();
00421     $spellChecker = t3lib_div::makeInstance('tx_rtehtmlarea_pi1');
00422     $spellChecker->main();
00423 }
00424 
00425 ?>