From 4ca10b8d511d85a4d575af355b0a6739d5a05958 Mon Sep 17 00:00:00 2001 From: thomascube Date: Fri, 15 Aug 2008 21:47:31 +0000 Subject: [PATCH] Enable spellchecker for HTML editor --- CHANGELOG | 1 + bin/html2text.php | 2 +- program/js/app.js | 31 +- program/js/editor.js | 46 +- .../spellchecker/classes/EnchantSpell.php | 66 ++ .../spellchecker/classes/GoogleSpell.php | 158 +++++ .../plugins/spellchecker/classes/PSpell.php | 81 +++ .../spellchecker/classes/PSpellShell.php | 112 ++++ .../spellchecker/classes/SpellChecker.php | 61 ++ .../spellchecker/classes/utils/JSON.php | 595 ++++++++++++++++++ .../spellchecker/classes/utils/Logger.php | 268 ++++++++ .../tiny_mce/plugins/spellchecker/config.php | 33 + .../plugins/spellchecker/editor_plugin.js | 2 +- .../plugins/spellchecker/editor_plugin_src.js | 4 +- .../plugins/spellchecker/includes/general.php | 98 +++ .../js/tiny_mce/plugins/spellchecker/rpc.php | 111 ++++ program/steps/mail/compose.inc | 19 +- 17 files changed, 1643 insertions(+), 45 deletions(-) create mode 100755 program/js/tiny_mce/plugins/spellchecker/classes/EnchantSpell.php create mode 100755 program/js/tiny_mce/plugins/spellchecker/classes/GoogleSpell.php create mode 100755 program/js/tiny_mce/plugins/spellchecker/classes/PSpell.php create mode 100755 program/js/tiny_mce/plugins/spellchecker/classes/PSpellShell.php create mode 100755 program/js/tiny_mce/plugins/spellchecker/classes/SpellChecker.php create mode 100755 program/js/tiny_mce/plugins/spellchecker/classes/utils/JSON.php create mode 100755 program/js/tiny_mce/plugins/spellchecker/classes/utils/Logger.php create mode 100755 program/js/tiny_mce/plugins/spellchecker/config.php create mode 100755 program/js/tiny_mce/plugins/spellchecker/includes/general.php create mode 100755 program/js/tiny_mce/plugins/spellchecker/rpc.php diff --git a/CHANGELOG b/CHANGELOG index 4a0168670..d42fca99b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ CHANGELOG RoundCube Webmail - Use current mailbox name in template (#1485256) - Better fix for skipping untagged responses (#1485261) - Added pspell support patch by Kris Steinhoff (#1483960) +- Enable spellchecker for HTML editor (#1485114) 2008/08/09 (alec) ---------- diff --git a/bin/html2text.php b/bin/html2text.php index 0f0e6ae14..2650bf2d6 100644 --- a/bin/html2text.php +++ b/bin/html2text.php @@ -6,6 +6,6 @@ require INSTALL_PATH.'program/include/iniset.php'; $converter = new html2text($HTTP_RAW_POST_DATA); header('Content-Type: text/plain; charset=UTF-8'); -print html_entity_decode($converter->get_text(), ENT_COMPAT, 'UTF-8'); +print html_entity_decode(trim($converter->get_text()), ENT_COMPAT, 'UTF-8'); ?> diff --git a/program/js/app.js b/program/js/app.js index 998efd7a2..c82ebf948 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -195,6 +195,8 @@ function rcube_webmail() { this.env.spellcheck.spelling_state_observer = function(s){ ref.set_spellcheck_state(s); }; this.set_spellcheck_state('ready'); + if (rcube_find_object('_is_html').value == '1') + this.display_spellcheck_controls(false); } if (this.env.drafts_mailbox) this.enable_command('savedraft', true); @@ -204,9 +206,9 @@ function rcube_webmail() this.enable_command('select-all', 'select-none', 'expunge', true); if (this.env.messagecount - && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox - || this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter)) - || this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter)))) + && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox + || this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter)) + || this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter)))) this.enable_command('purge', true); this.set_page_buttons(); @@ -855,11 +857,13 @@ function rcube_webmail() break; case 'spellcheck': - if (this.env.spellcheck && this.env.spellcheck.spellCheck && this.spellcheck_ready) - { + if (window.tinyMCE && tinyMCE.get('compose-body')) { + tinyMCE.execCommand('mceSpellCheck', true); + } + else if (this.env.spellcheck && this.env.spellcheck.spellCheck && this.spellcheck_ready) { this.env.spellcheck.spellCheck(this.env.spellcheck.check_link); this.set_spellcheck_state('checking'); - } + } break; case 'savedraft': @@ -1899,16 +1903,12 @@ function rcube_webmail() } // check for empty body - if ((!window.tinyMCE || !tinyMCE.get('compose-body')) - && input_message.value == '' - && !confirm(this.get_label('nobodywarning'))) + if ((!window.tinyMCE || !tinyMCE.get('compose-body')) && input_message.value == '' && !confirm(this.get_label('nobodywarning'))) { input_message.focus(); return false; } - else if (window.tinyMCE && tinyMCE.get('compose-body') - && !tinyMCE.get('compose-body').getContent() - && !confirm(this.get_label('nobodywarning'))) + else if (window.tinyMCE && tinyMCE.get('compose-body') && !tinyMCE.get('compose-body').getContent() && !confirm(this.get_label('nobodywarning'))) { tinyMCE.get('compose-body').focus(); return false; @@ -1917,6 +1917,13 @@ function rcube_webmail() return true; }; + this.display_spellcheck_controls = function(vis) + { + if (this.env.spellcheck) { + this.env.spellcheck.check_link.style.visibility = vis ? 'visible' : 'hidden'; + this.env.spellcheck.switch_lan_pic.style.visibility = vis ? 'visible' : 'hidden'; + } + }; this.set_spellcheck_state = function(s) { diff --git a/program/js/editor.js b/program/js/editor.js index 6fdbadccd..38c9b7168 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -15,26 +15,28 @@ // Initialize the message editor -function rcmail_editor_init(skin_path, editor_lang) - { - tinyMCE.init({ mode : "textareas", - editor_selector : "mce_editor", - accessibility_focus : false, - apply_source_formatting : true, - theme : "advanced", - language : editor_lang, - plugins : "emotions,media,nonbreaking,table,searchreplace,visualchars,directionality", - theme_advanced_buttons1 : "bold,italic,underline,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,bullist,numlist,outdent,indent,separator,link,unlink,emotions,charmap,code,forecolor,backcolor,fontselect,fontsizeselect, separator,undo,redo,image,media,ltr,rtl", - theme_advanced_buttons2 : "", - theme_advanced_buttons3 : "", - theme_advanced_toolbar_location : "top", - theme_advanced_toolbar_align : "left", - extended_valid_elements : "font[face|size|color|style],span[id|class|align|style]", - content_css : skin_path + "/editor_content.css", - external_image_list_url : "program/js/editor_images.js", - rc_client: rcube_webmail_client - }); - } +function rcmail_editor_init(skin_path, editor_lang, spellcheck) +{ + tinyMCE.init({ + mode : "textareas", + editor_selector : "mce_editor", + accessibility_focus : false, + apply_source_formatting : true, + theme : "advanced", + language : editor_lang, + plugins : "emotions,media,nonbreaking,table,searchreplace,visualchars,directionality" + (spellcheck ? ",spellchecker" : ""), + theme_advanced_buttons1 : "bold,italic,underline,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,bullist,numlist,outdent,indent,separator,link,unlink,emotions,charmap,code,forecolor,backcolor,fontselect,fontsizeselect, separator" + (spellcheck ? ",spellchecker" : "") + ",undo,redo,image,media,ltr,rtl", + theme_advanced_buttons2 : "", + theme_advanced_buttons3 : "", + theme_advanced_toolbar_location : "top", + theme_advanced_toolbar_align : "left", + extended_valid_elements : "font[face|size|color|style],span[id|class|align|style]", + content_css : skin_path + "/editor_content.css", + external_image_list_url : "program/js/editor_images.js", + spellchecker_languages : (rcmail.env.spellcheck_langs ? rcmail.env.spellcheck_langs : "Dansk=da,Deutsch=de,+English=en,Espanol=es,Francais=fr,Italiano=it,Nederlands=nl,Polski=pl,Portugues=pt,Suomi=fi,Svenska=sv"), + rc_client: rcube_webmail_client + }); +} // Toggle between the HTML and Plain Text editors @@ -63,6 +65,7 @@ function rcmail_toggle_editor(toggler) composeElement.value = htmlText; tinyMCE.execCommand('mceAddControl', true, 'compose-body'); htmlFlag.value = "1"; + rcmail.display_spellcheck_controls(false); } else { @@ -72,6 +75,7 @@ function rcmail_toggle_editor(toggler) rcmail_html2plain(existingHtml); tinyMCE.execCommand('mceRemoveControl', true, 'compose-body'); htmlFlag.value = "0"; + rcmail.display_spellcheck_controls(true); } } @@ -82,7 +86,7 @@ function rcmail_html2plain(htmlText) http_request.onerror = function(o) { rcmail_handle_toggle_error(o); }; http_request.oncomplete = function(o) { rcmail_set_text_value(o); }; var url = rcmail.env.bin_path+'html2text.php'; - console.log('HTTP request: ' + url); + //console.log('HTTP request: ' + url); http_request.POST(url, htmlText, 'application/octet-stream'); } diff --git a/program/js/tiny_mce/plugins/spellchecker/classes/EnchantSpell.php b/program/js/tiny_mce/plugins/spellchecker/classes/EnchantSpell.php new file mode 100755 index 000000000..fb6e67c43 --- /dev/null +++ b/program/js/tiny_mce/plugins/spellchecker/classes/EnchantSpell.php @@ -0,0 +1,66 @@ += 1.4.1 + * @param Array $words Array of words to check. + * @return Array of misspelled words. + */ + function &checkWords($lang, $words) { + $r = enchant_broker_init(); + + if (enchant_broker_dict_exists($r,$lang)) { + $d = enchant_broker_request_dict($r, $lang); + + $returnData = array(); + foreach($words as $key => $value) { + $correct = enchant_dict_check($d, $value); + if(!$correct) { + $returnData[] = trim($value); + } + } + + return $returnData; + enchant_broker_free_dict($d); + } else { + + } + enchant_broker_free($r); + } + + /** + * Returns suggestions for a specific word. + * + * @param String $lang Selected language code (like en_US or de_DE). Shortcodes like "en" and "de" work with enchant >= 1.4.1 + * @param String $word Specific word to get suggestions for. + * @return Array of suggestions for the specified word. + */ + function &getSuggestions($lang, $word) { + $r = enchant_broker_init(); + $suggs = array(); + + if (enchant_broker_dict_exists($r,$lang)) { + $d = enchant_broker_request_dict($r, $lang); + $suggs = enchant_dict_suggest($d, $word); + + enchant_broker_free_dict($d); + } else { + + } + enchant_broker_free($r); + + return $suggs; + } +} + +?> diff --git a/program/js/tiny_mce/plugins/spellchecker/classes/GoogleSpell.php b/program/js/tiny_mce/plugins/spellchecker/classes/GoogleSpell.php new file mode 100755 index 000000000..53d4300e3 --- /dev/null +++ b/program/js/tiny_mce/plugins/spellchecker/classes/GoogleSpell.php @@ -0,0 +1,158 @@ +_getMatches($lang, $wordstr); + $words = array(); + + for ($i=0; $i_unhtmlentities(mb_substr($wordstr, $matches[$i][1], $matches[$i][2], "UTF-8")); + + return $words; + } + + /** + * Returns suggestions of for a specific word. + * + * @param {String} $lang Language code like sv or en. + * @param {String} $word Specific word to get suggestions for. + * @return {Array} Array of suggestions for the specified word. + */ + function &getSuggestions($lang, $word) { + $sug = array(); + $osug = array(); + $matches = $this->_getMatches($lang, $word); + + if (count($matches) > 0) + $sug = explode("\t", utf8_encode($this->_unhtmlentities($matches[0][4]))); + + // Remove empty + foreach ($sug as $item) { + if ($item) + $osug[] = $item; + } + + return $osug; + } + + function &_getMatches($lang, $str) { + $server = "www.google.com"; + $port = 443; + $path = "/tbproxy/spell?lang=" . $lang . "&hl=en"; + $host = "www.google.com"; + $url = "https://" . $server; + + // Setup XML request + $xml = '' . $str . ''; + + $header = "POST ".$path." HTTP/1.0 \r\n"; + $header .= "MIME-Version: 1.0 \r\n"; + $header .= "Content-type: application/PTI26 \r\n"; + $header .= "Content-length: ".strlen($xml)." \r\n"; + $header .= "Content-transfer-encoding: text \r\n"; + $header .= "Request-number: 1 \r\n"; + $header .= "Document-type: Request \r\n"; + $header .= "Interface-Version: Test 1.4 \r\n"; + $header .= "Connection: close \r\n\r\n"; + $header .= $xml; + + // Use curl if it exists + if (function_exists('curl_init')) { + // Use curl + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL,$url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $header); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + $xml = curl_exec($ch); + curl_close($ch); + } else { + // Use raw sockets + $fp = fsockopen("ssl://" . $server, $port, $errno, $errstr, 30); + if ($fp) { + // Send request + fwrite($fp, $header); + + // Read response + $xml = ""; + while (!feof($fp)) + $xml .= fgets($fp, 128); + + fclose($fp); + } else + echo "Could not open SSL connection to google."; + } + + // Grab and parse content + $matches = array(); + preg_match_all('/([^<]*)<\/c>/', $xml, $matches, PREG_SET_ORDER); + + return $matches; + } + + function _unhtmlentities($string) { + $string = preg_replace('~&#x([0-9a-f]+);~ei', 'chr(hexdec("\\1"))', $string); + $string = preg_replace('~&#([0-9]+);~e', 'chr(\\1)', $string); + + $trans_tbl = get_html_translation_table(HTML_ENTITIES); + $trans_tbl = array_flip($trans_tbl); + + return strtr($string, $trans_tbl); + } +} + +// Patch in multibyte support +if (!function_exists('mb_substr')) { + function mb_substr($str, $start, $len = '', $encoding="UTF-8"){ + $limit = strlen($str); + + for ($s = 0; $start > 0;--$start) {// found the real start + if ($s >= $limit) + break; + + if ($str[$s] <= "\x7F") + ++$s; + else { + ++$s; // skip length + + while ($str[$s] >= "\x80" && $str[$s] <= "\xBF") + ++$s; + } + } + + if ($len == '') + return substr($str, $s); + else + for ($e = $s; $len > 0; --$len) {//found the real end + if ($e >= $limit) + break; + + if ($str[$e] <= "\x7F") + ++$e; + else { + ++$e;//skip length + + while ($str[$e] >= "\x80" && $str[$e] <= "\xBF" && $e < $limit) + ++$e; + } + } + + return substr($str, $s, $e - $s); + } +} + +?> diff --git a/program/js/tiny_mce/plugins/spellchecker/classes/PSpell.php b/program/js/tiny_mce/plugins/spellchecker/classes/PSpell.php new file mode 100755 index 000000000..45448d070 --- /dev/null +++ b/program/js/tiny_mce/plugins/spellchecker/classes/PSpell.php @@ -0,0 +1,81 @@ +_getPLink($lang); + + $outWords = array(); + foreach ($words as $word) { + if (!pspell_check($plink, trim($word))) + $outWords[] = utf8_encode($word); + } + + return $outWords; + } + + /** + * Returns suggestions of for a specific word. + * + * @param {String} $lang Language code like sv or en. + * @param {String} $word Specific word to get suggestions for. + * @return {Array} Array of suggestions for the specified word. + */ + function &getSuggestions($lang, $word) { + $words = pspell_suggest($this->_getPLink($lang), $word); + + for ($i=0; $ithrowError("PSpell support not found in PHP installation."); + + // Setup PSpell link + $plink = pspell_new( + $lang, + $this->_config['PSpell.spelling'], + $this->_config['PSpell.jargon'], + $this->_config['PSpell.encoding'], + $this->_config['PSpell.mode'] + ); + + // Setup PSpell link +/* if (!$plink) { + $pspellConfig = pspell_config_create( + $lang, + $this->_config['PSpell.spelling'], + $this->_config['PSpell.jargon'], + $this->_config['PSpell.encoding'] + ); + + $plink = pspell_new_config($pspell_config); + }*/ + + if (!$plink) + $this->throwError("No PSpell link found opened."); + + return $plink; + } +} + +?> diff --git a/program/js/tiny_mce/plugins/spellchecker/classes/PSpellShell.php b/program/js/tiny_mce/plugins/spellchecker/classes/PSpellShell.php new file mode 100755 index 000000000..0bc57de7e --- /dev/null +++ b/program/js/tiny_mce/plugins/spellchecker/classes/PSpellShell.php @@ -0,0 +1,112 @@ +_getCMD($lang); + + if ($fh = fopen($this->_tmpfile, "w")) { + fwrite($fh, "!\n"); + + foreach($words as $key => $value) + fwrite($fh, "^" . $value . "\n"); + + fclose($fh); + } else + $this->throwError("PSpell support was not found."); + + $data = shell_exec($cmd); + @unlink($this->_tmpfile); + + $returnData = array(); + $dataArr = preg_split("/[\r\n]/", $data, -1, PREG_SPLIT_NO_EMPTY); + + foreach ($dataArr as $dstr) { + $matches = array(); + + // Skip this line. + if (strpos($dstr, "@") === 0) + continue; + + preg_match("/\& ([^ ]+) .*/i", $dstr, $matches); + + if (!empty($matches[1])) + $returnData[] = utf8_encode(trim($matches[1])); + } + + return $returnData; + } + + /** + * Returns suggestions of for a specific word. + * + * @param {String} $lang Language code like sv or en. + * @param {String} $word Specific word to get suggestions for. + * @return {Array} Array of suggestions for the specified word. + */ + function &getSuggestions($lang, $word) { + $cmd = $this->_getCMD($lang); + + if (function_exists("mb_convert_encoding")) + $word = mb_convert_encoding($word, "ISO-8859-1", mb_detect_encoding($word, "UTF-8")); + else + $word = utf8_encode($word); + + if ($fh = fopen($this->_tmpfile, "w")) { + fwrite($fh, "!\n"); + fwrite($fh, "^$word\n"); + fclose($fh); + } else + $this->throwError("Error opening tmp file."); + + $data = shell_exec($cmd); + @unlink($this->_tmpfile); + + $returnData = array(); + $dataArr = preg_split("/\n/", $data, -1, PREG_SPLIT_NO_EMPTY); + + foreach($dataArr as $dstr) { + $matches = array(); + + // Skip this line. + if (strpos($dstr, "@") === 0) + continue; + + preg_match("/\&[^:]+:(.*)/i", $dstr, $matches); + + if (!empty($matches[1])) { + $words = array_slice(explode(',', $matches[1]), 0, 10); + + for ($i=0; $i_tmpfile = tempnam($this->_config['PSpellShell.tmp'], "tinyspell"); + + if(preg_match("#win#i", php_uname())) + return $this->_config['PSpellShell.aspell'] . " -a --lang=". escapeshellarg($lang) . " --encoding=utf-8 -H < " . $this->_tmpfile . " 2>&1"; + + return "cat ". $this->_tmpfile ." | " . $this->_config['PSpellShell.aspell'] . " -a --encoding=utf-8 -H --lang=". escapeshellarg($lang); + } +} + +?> diff --git a/program/js/tiny_mce/plugins/spellchecker/classes/SpellChecker.php b/program/js/tiny_mce/plugins/spellchecker/classes/SpellChecker.php new file mode 100755 index 000000000..d6800391a --- /dev/null +++ b/program/js/tiny_mce/plugins/spellchecker/classes/SpellChecker.php @@ -0,0 +1,61 @@ +_config = $config; + } + + /** + * Simple loopback function everything that gets in will be send back. + * + * @param $args.. Arguments. + * @return {Array} Array of all input arguments. + */ + function &loopback(/* args.. */) { + return func_get_args(); + } + + /** + * Spellchecks an array of words. + * + * @param {String} $lang Language code like sv or en. + * @param {Array} $words Array of words to spellcheck. + * @return {Array} Array of misspelled words. + */ + function &checkWords($lang, $words) { + return $words; + } + + /** + * Returns suggestions of for a specific word. + * + * @param {String} $lang Language code like sv or en. + * @param {String} $word Specific word to get suggestions for. + * @return {Array} Array of suggestions for the specified word. + */ + function &getSuggestions($lang, $word) { + return array(); + } + + /** + * Throws an error message back to the user. This will stop all execution. + * + * @param {String} $str Message to send back to user. + */ + function throwError($str) { + die('{"result":null,"id":null,"error":{"errstr":"' . addslashes($str) . '","errfile":"","errline":null,"errcontext":"","level":"FATAL"}}'); + } +} + +?> diff --git a/program/js/tiny_mce/plugins/spellchecker/classes/utils/JSON.php b/program/js/tiny_mce/plugins/spellchecker/classes/utils/JSON.php new file mode 100755 index 000000000..1c4611676 --- /dev/null +++ b/program/js/tiny_mce/plugins/spellchecker/classes/utils/JSON.php @@ -0,0 +1,595 @@ +_data = $data; + $this->_len = strlen($data); + $this->_pos = -1; + $this->_location = JSON_IN_BETWEEN; + $this->_lastLocations = array(); + $this->_needProp = false; + } + + function getToken() { + return $this->_token; + } + + function getLocation() { + return $this->_location; + } + + function getTokenName() { + switch ($this->_token) { + case JSON_BOOL: + return 'JSON_BOOL'; + + case JSON_INT: + return 'JSON_INT'; + + case JSON_STR: + return 'JSON_STR'; + + case JSON_FLOAT: + return 'JSON_FLOAT'; + + case JSON_NULL: + return 'JSON_NULL'; + + case JSON_START_OBJ: + return 'JSON_START_OBJ'; + + case JSON_END_OBJ: + return 'JSON_END_OBJ'; + + case JSON_START_ARRAY: + return 'JSON_START_ARRAY'; + + case JSON_END_ARRAY: + return 'JSON_END_ARRAY'; + + case JSON_KEY: + return 'JSON_KEY'; + } + + return 'UNKNOWN'; + } + + function getValue() { + return $this->_value; + } + + function readToken() { + $chr = $this->read(); + + if ($chr != null) { + switch ($chr) { + case '[': + $this->_lastLocation[] = $this->_location; + $this->_location = JSON_IN_ARRAY; + $this->_token = JSON_START_ARRAY; + $this->_value = null; + $this->readAway(); + return true; + + case ']': + $this->_location = array_pop($this->_lastLocation); + $this->_token = JSON_END_ARRAY; + $this->_value = null; + $this->readAway(); + + if ($this->_location == JSON_IN_OBJECT) + $this->_needProp = true; + + return true; + + case '{': + $this->_lastLocation[] = $this->_location; + $this->_location = JSON_IN_OBJECT; + $this->_needProp = true; + $this->_token = JSON_START_OBJ; + $this->_value = null; + $this->readAway(); + return true; + + case '}': + $this->_location = array_pop($this->_lastLocation); + $this->_token = JSON_END_OBJ; + $this->_value = null; + $this->readAway(); + + if ($this->_location == JSON_IN_OBJECT) + $this->_needProp = true; + + return true; + + // String + case '"': + case '\'': + return $this->_readString($chr); + + // Null + case 'n': + return $this->_readNull(); + + // Bool + case 't': + case 'f': + return $this->_readBool($chr); + + default: + // Is number + if (is_numeric($chr) || $chr == '-' || $chr == '.') + return $this->_readNumber($chr); + + return true; + } + } + + return false; + } + + function _readBool($chr) { + $this->_token = JSON_BOOL; + $this->_value = $chr == 't'; + + if ($chr == 't') + $this->skip(3); // rue + else + $this->skip(4); // alse + + $this->readAway(); + + if ($this->_location == JSON_IN_OBJECT && !$this->_needProp) + $this->_needProp = true; + + return true; + } + + function _readNull() { + $this->_token = JSON_NULL; + $this->_value = null; + + $this->skip(3); // ull + $this->readAway(); + + if ($this->_location == JSON_IN_OBJECT && !$this->_needProp) + $this->_needProp = true; + + return true; + } + + function _readString($quote) { + $output = ""; + $this->_token = JSON_STR; + $endString = false; + + while (($chr = $this->peek()) != -1) { + switch ($chr) { + case '\\': + // Read away slash + $this->read(); + + // Read escape code + $chr = $this->read(); + switch ($chr) { + case 't': + $output .= "\t"; + break; + + case 'b': + $output .= "\b"; + break; + + case 'f': + $output .= "\f"; + break; + + case 'r': + $output .= "\r"; + break; + + case 'n': + $output .= "\n"; + break; + + case 'u': + $output .= $this->_int2utf8(hexdec($this->read(4))); + break; + + default: + $output .= $chr; + break; + } + + break; + + case '\'': + case '"': + if ($chr == $quote) + $endString = true; + + $chr = $this->read(); + if ($chr != -1 && $chr != $quote) + $output .= $chr; + + break; + + default: + $output .= $this->read(); + } + + // String terminated + if ($endString) + break; + } + + $this->readAway(); + $this->_value = $output; + + // Needed a property + if ($this->_needProp) { + $this->_token = JSON_KEY; + $this->_needProp = false; + return true; + } + + if ($this->_location == JSON_IN_OBJECT && !$this->_needProp) + $this->_needProp = true; + + return true; + } + + function _int2utf8($int) { + $int = intval($int); + + switch ($int) { + case 0: + return chr(0); + + case ($int & 0x7F): + return chr($int); + + case ($int & 0x7FF): + return chr(0xC0 | (($int >> 6) & 0x1F)) . chr(0x80 | ($int & 0x3F)); + + case ($int & 0xFFFF): + return chr(0xE0 | (($int >> 12) & 0x0F)) . chr(0x80 | (($int >> 6) & 0x3F)) . chr (0x80 | ($int & 0x3F)); + + case ($int & 0x1FFFFF): + return chr(0xF0 | ($int >> 18)) . chr(0x80 | (($int >> 12) & 0x3F)) . chr(0x80 | (($int >> 6) & 0x3F)) . chr(0x80 | ($int & 0x3F)); + } + } + + function _readNumber($start) { + $value = ""; + $isFloat = false; + + $this->_token = JSON_INT; + $value .= $start; + + while (($chr = $this->peek()) != -1) { + if (is_numeric($chr) || $chr == '-' || $chr == '.') { + if ($chr == '.') + $isFloat = true; + + $value .= $this->read(); + } else + break; + } + + $this->readAway(); + + if ($isFloat) { + $this->_token = JSON_FLOAT; + $this->_value = floatval($value); + } else + $this->_value = intval($value); + + if ($this->_location == JSON_IN_OBJECT && !$this->_needProp) + $this->_needProp = true; + + return true; + } + + function readAway() { + while (($chr = $this->peek()) != null) { + if ($chr != ':' && $chr != ',' && $chr != ' ') + return; + + $this->read(); + } + } + + function read($len = 1) { + if ($this->_pos < $this->_len) { + if ($len > 1) { + $str = substr($this->_data, $this->_pos + 1, $len); + $this->_pos += $len; + + return $str; + } else + return $this->_data[++$this->_pos]; + } + + return null; + } + + function skip($len) { + $this->_pos += $len; + } + + function peek() { + if ($this->_pos < $this->_len) + return $this->_data[$this->_pos + 1]; + + return null; + } +} + +/** + * This class handles JSON stuff. + * + * @package MCManager.utils + */ +class Moxiecode_JSON { + function Moxiecode_JSON() { + } + + function decode($input) { + $reader = new Moxiecode_JSONReader($input); + + return $this->readValue($reader); + } + + function readValue(&$reader) { + $this->data = array(); + $this->parents = array(); + $this->cur =& $this->data; + $key = null; + $loc = JSON_IN_ARRAY; + + while ($reader->readToken()) { + switch ($reader->getToken()) { + case JSON_STR: + case JSON_INT: + case JSON_BOOL: + case JSON_FLOAT: + case JSON_NULL: + switch ($reader->getLocation()) { + case JSON_IN_OBJECT: + $this->cur[$key] = $reader->getValue(); + break; + + case JSON_IN_ARRAY: + $this->cur[] = $reader->getValue(); + break; + + default: + return $reader->getValue(); + } + break; + + case JSON_KEY: + $key = $reader->getValue(); + break; + + case JSON_START_OBJ: + case JSON_START_ARRAY: + if ($loc == JSON_IN_OBJECT) + $this->addArray($key); + else + $this->addArray(null); + + $cur =& $obj; + + $loc = $reader->getLocation(); + break; + + case JSON_END_OBJ: + case JSON_END_ARRAY: + $loc = $reader->getLocation(); + + if (count($this->parents) > 0) { + $this->cur =& $this->parents[count($this->parents) - 1]; + array_pop($this->parents); + } + break; + } + } + + return $this->data[0]; + } + + // This method was needed since PHP is crapy and doesn't have pointers/references + function addArray($key) { + $this->parents[] =& $this->cur; + $ar = array(); + + if ($key) + $this->cur[$key] =& $ar; + else + $this->cur[] =& $ar; + + $this->cur =& $ar; + } + + function getDelim($index, &$reader) { + switch ($reader->getLocation()) { + case JSON_IN_ARRAY: + case JSON_IN_OBJECT: + if ($index > 0) + return ","; + break; + } + + return ""; + } + + function encode($input) { + switch (gettype($input)) { + case 'boolean': + return $input ? 'true' : 'false'; + + case 'integer': + return (int) $input; + + case 'float': + case 'double': + return (float) $input; + + case 'NULL': + return 'null'; + + case 'string': + return $this->encodeString($input); + + case 'array': + return $this->_encodeArray($input); + + case 'object': + return $this->_encodeArray(get_object_vars($input)); + } + + return ''; + } + + function encodeString($input) { + // Needs to be escaped + if (preg_match('/[^a-zA-Z0-9]/', $input)) { + $output = ''; + + for ($i=0; $i_utf82utf16($char))); + } if (($byte & 0xF0) == 0xE0) { + $char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2])); + $i += 2; + $output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char))); + } if (($byte & 0xF8) == 0xF0) { + $char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2], ord($input[$i + 3]))); + $i += 3; + $output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char))); + } if (($byte & 0xFC) == 0xF8) { + $char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2], ord($input[$i + 3]), ord($input[$i + 4]))); + $i += 4; + $output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char))); + } if (($byte & 0xFE) == 0xFC) { + $char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2], ord($input[$i + 3]), ord($input[$i + 4]), ord($input[$i + 5]))); + $i += 5; + $output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char))); + } else if ($byte < 128) + $output .= $input[$i]; + } + } + + return '"' . $output . '"'; + } + + return '"' . $input . '"'; + } + + function _utf82utf16($utf8) { + if (function_exists('mb_convert_encoding')) + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + + switch (strlen($utf8)) { + case 1: + return $utf8; + + case 2: + return chr(0x07 & (ord($utf8[0]) >> 2)) . chr((0xC0 & (ord($utf8[0]) << 6)) | (0x3F & ord($utf8[1]))); + + case 3: + return chr((0xF0 & (ord($utf8[0]) << 4)) | (0x0F & (ord($utf8[1]) >> 2))) . chr((0xC0 & (ord($utf8[1]) << 6)) | (0x7F & ord($utf8[2]))); + } + + return ''; + } + + function _encodeArray($input) { + $output = ''; + $isIndexed = true; + + $keys = array_keys($input); + for ($i=0; $iencodeString($keys[$i]) . ':' . $this->encode($input[$keys[$i]]); + $isIndexed = false; + } else + $output .= $this->encode($input[$keys[$i]]); + + if ($i != count($keys) - 1) + $output .= ','; + } + + return $isIndexed ? '[' . $output . ']' : '{' . $output . '}'; + } +} + +?> diff --git a/program/js/tiny_mce/plugins/spellchecker/classes/utils/Logger.php b/program/js/tiny_mce/plugins/spellchecker/classes/utils/Logger.php new file mode 100755 index 000000000..bc501ea75 --- /dev/null +++ b/program/js/tiny_mce/plugins/spellchecker/classes/utils/Logger.php @@ -0,0 +1,268 @@ +_path = ""; + $this->_filename = "{level}.log"; + $this->setMaxSize("100k"); + $this->_maxFiles = 10; + $this->_level = MC_LOGGER_DEBUG; + $this->_format = "[{time}] [{level}] {message}"; + } + + /** + * Sets the current log level, use the MC_LOGGER constants. + * + * @param int $level Log level instance for example MC_LOGGER_DEBUG. + */ + function setLevel($level) { + if (is_string($level)) { + switch (strtolower($level)) { + case "debug": + $level = MC_LOGGER_DEBUG; + break; + + case "info": + $level = MC_LOGGER_INFO; + break; + + case "warn": + case "warning": + $level = MC_LOGGER_WARN; + break; + + case "error": + $level = MC_LOGGER_ERROR; + break; + + case "fatal": + $level = MC_LOGGER_FATAL; + break; + + default: + $level = MC_LOGGER_FATAL; + } + } + + $this->_level = $level; + } + + /** + * Returns the current log level for example MC_LOGGER_DEBUG. + * + * @return int Current log level for example MC_LOGGER_DEBUG. + */ + function getLevel() { + return $this->_level; + } + + function setPath($path) { + $this->_path = $path; + } + + function getPath() { + return $this->_path; + } + + function setFileName($file_name) { + $this->_filename = $file_name; + } + + function getFileName() { + return $this->_filename; + } + + function setFormat($format) { + $this->_format = $format; + } + + function getFormat() { + return $this->_format; + } + + function setMaxSize($size) { + // Fix log max size + $logMaxSizeBytes = intval(preg_replace("/[^0-9]/", "", $size)); + + // Is KB + if (strpos((strtolower($size)), "k") > 0) + $logMaxSizeBytes *= 1024; + + // Is MB + if (strpos((strtolower($size)), "m") > 0) + $logMaxSizeBytes *= (1024 * 1024); + + $this->_maxSizeBytes = $logMaxSizeBytes; + $this->_maxSize = $size; + } + + function getMaxSize() { + return $this->_maxSize; + } + + function setMaxFiles($max_files) { + $this->_maxFiles = $max_files; + } + + function getMaxFiles() { + return $this->_maxFiles; + } + + function debug($msg) { + $args = func_get_args(); + $this->_logMsg(MC_LOGGER_DEBUG, implode(', ', $args)); + } + + function info($msg) { + $args = func_get_args(); + $this->_logMsg(MC_LOGGER_INFO, implode(', ', $args)); + } + + function warn($msg) { + $args = func_get_args(); + $this->_logMsg(MC_LOGGER_WARN, implode(', ', $args)); + } + + function error($msg) { + $args = func_get_args(); + $this->_logMsg(MC_LOGGER_ERROR, implode(', ', $args)); + } + + function fatal($msg) { + $args = func_get_args(); + $this->_logMsg(MC_LOGGER_FATAL, implode(', ', $args)); + } + + function isDebugEnabled() { + return $this->_level >= MC_LOGGER_DEBUG; + } + + function isInfoEnabled() { + return $this->_level >= MC_LOGGER_INFO; + } + + function isWarnEnabled() { + return $this->_level >= MC_LOGGER_WARN; + } + + function isErrorEnabled() { + return $this->_level >= MC_LOGGER_ERROR; + } + + function isFatalEnabled() { + return $this->_level >= MC_LOGGER_FATAL; + } + + function _logMsg($level, $message) { + $roll = false; + + if ($level < $this->_level) + return; + + $logFile = $this->toOSPath($this->_path . "/" . $this->_filename); + + switch ($level) { + case MC_LOGGER_DEBUG: + $levelName = "DEBUG"; + break; + + case MC_LOGGER_INFO: + $levelName = "INFO"; + break; + + case MC_LOGGER_WARN: + $levelName = "WARN"; + break; + + case MC_LOGGER_ERROR: + $levelName = "ERROR"; + break; + + case MC_LOGGER_FATAL: + $levelName = "FATAL"; + break; + } + + $logFile = str_replace('{level}', strtolower($levelName), $logFile); + + $text = $this->_format; + $text = str_replace('{time}', date("Y-m-d H:i:s"), $text); + $text = str_replace('{level}', strtolower($levelName), $text); + $text = str_replace('{message}', $message, $text); + $message = $text . "\r\n"; + + // Check filesize + if (file_exists($logFile)) { + $size = @filesize($logFile); + + if ($size + strlen($message) > $this->_maxSizeBytes) + $roll = true; + } + + // Roll if the size is right + if ($roll) { + for ($i=$this->_maxFiles-1; $i>=1; $i--) { + $rfile = $this->toOSPath($logFile . "." . $i); + $nfile = $this->toOSPath($logFile . "." . ($i+1)); + + if (@file_exists($rfile)) + @rename($rfile, $nfile); + } + + @rename($logFile, $this->toOSPath($logFile . ".1")); + + // Delete last logfile + $delfile = $this->toOSPath($logFile . "." . ($this->_maxFiles + 1)); + if (@file_exists($delfile)) + @unlink($delfile); + } + + // Append log line + if (($fp = @fopen($logFile, "a")) != null) { + @fputs($fp, $message); + @fflush($fp); + @fclose($fp); + } + } + + /** + * Converts a Unix path to OS specific path. + * + * @param String $path Unix path to convert. + */ + function toOSPath($path) { + return str_replace("/", DIRECTORY_SEPARATOR, $path); + } +} + +?> \ No newline at end of file diff --git a/program/js/tiny_mce/plugins/spellchecker/config.php b/program/js/tiny_mce/plugins/spellchecker/config.php new file mode 100755 index 000000000..827749e48 --- /dev/null +++ b/program/js/tiny_mce/plugins/spellchecker/config.php @@ -0,0 +1,33 @@ +get('spellcheck_engine') == 'pspell' ? 'PSpell' : 'GoogleSpell'; + + /** end RoundCube specific code */ + + // General settings + //$config['general.engine'] = 'GoogleSpell'; + //$config['general.engine'] = 'PSpell'; + //$config['general.engine'] = 'PSpellShell'; + //$config['general.remote_rpc_url'] = 'http://some.other.site/some/url/rpc.php'; + + // PSpell settings + $config['PSpell.mode'] = PSPELL_FAST; + $config['PSpell.spelling'] = ""; + $config['PSpell.jargon'] = ""; + $config['PSpell.encoding'] = ""; + + // PSpellShell settings + $config['PSpellShell.mode'] = PSPELL_FAST; + $config['PSpellShell.aspell'] = '/usr/bin/aspell'; + $config['PSpellShell.tmp'] = '/tmp'; + + // Windows PSpellShell settings + //$config['PSpellShell.aspell'] = '"c:\Program Files\Aspell\bin\aspell.exe"'; + //$config['PSpellShell.tmp'] = 'c:/temp'; +?> diff --git a/program/js/tiny_mce/plugins/spellchecker/editor_plugin.js b/program/js/tiny_mce/plugins/spellchecker/editor_plugin.js index 9cb679961..915fc400d 100644 --- a/program/js/tiny_mce/plugins/spellchecker/editor_plugin.js +++ b/program/js/tiny_mce/plugins/spellchecker/editor_plugin.js @@ -1 +1 @@ -(function(){var JSONRequest=tinymce.util.JSONRequest,each=tinymce.each,DOM=tinymce.DOM;tinymce.create('tinymce.plugins.SpellcheckerPlugin',{getInfo:function(){return{longname:'Spellchecker',author:'Moxiecode Systems AB',authorurl:'http://tinymce.moxiecode.com',infourl:'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker',version:tinymce.majorVersion+"."+tinymce.minorVersion};},init:function(ed,url){var t=this,cm;t.url=url;t.editor=ed;ed.addCommand('mceSpellCheck',function(){if(!t.active){ed.setProgressState(1);t._sendRPC('checkWords',[t.selectedLang,t._getWords()],function(r){if(r.length>0){t.active=1;t._markWords(r);ed.setProgressState(0);ed.nodeChanged();}else{ed.setProgressState(0);ed.windowManager.alert('spellchecker.no_mpell');}});}else t._done();});ed.onInit.add(function(){if(ed.settings.content_css!==false)ed.dom.loadCSS(url+'/css/content.css');});ed.onClick.add(t._showMenu,t);ed.onContextMenu.add(t._showMenu,t);ed.onBeforeGetContent.add(function(){if(t.active)t._removeWords();});ed.onNodeChange.add(function(ed,cm){cm.setActive('spellchecker',t.active);});ed.onSetContent.add(function(){t._done();});ed.onBeforeGetContent.add(function(){t._done();});ed.onBeforeExecCommand.add(function(ed,cmd){if(cmd=='mceFullScreen')t._done();});t.languages={};each(ed.getParam('spellchecker_languages','+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv','hash'),function(v,k){if(k.indexOf('+')===0){k=k.substring(1);t.selectedLang=v;}t.languages[k]=v;});},createControl:function(n,cm){var t=this,c,ed=t.editor;if(n=='spellchecker'){c=cm.createSplitButton(n,{title:'spellchecker.desc',cmd:'mceSpellCheck',scope:t});c.onRenderMenu.add(function(c,m){m.add({title:'spellchecker.langs','class':'mceMenuItemTitle'}).setDisabled(1);each(t.languages,function(v,k){var o={icon:1},mi;o.onclick=function(){mi.setSelected(1);t.selectedItem.setSelected(0);t.selectedItem=mi;t.selectedLang=v;};o.title=k;mi=m.add(o);mi.setSelected(v==t.selectedLang);if(v==t.selectedLang)t.selectedItem=mi;})});return c;}},_walk:function(n,f){var d=this.editor.getDoc(),w;if(d.createTreeWalker){w=d.createTreeWalker(n,NodeFilter.SHOW_TEXT,null,false);while((n=w.nextNode())!=null)f.call(this,n);}else tinymce.walk(n,f,'childNodes');},_getSeparators:function(){var re='',i,str=this.editor.getParam('spellchecker_word_separator_chars','\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}����������������\u201d\u201c');for(i=0;i$1$2');v=v.replace(r3,'$1$2');dom.replace(dom.create('span',{'class':'mceItemHidden'},v),n);}}});se.moveToBookmark(b);},_showMenu:function(ed,e){var t=this,ed=t.editor,m=t._menu,p1,dom=ed.dom,vp=dom.getViewPort(ed.getWin());if(!m){p1=DOM.getPos(ed.getContentAreaContainer());m=ed.controlManager.createDropMenu('spellcheckermenu',{offset_x:p1.x,offset_y:p1.y,'class':'mceNoIcons'});t._menu=m;}if(dom.hasClass(e.target,'mceItemHiddenSpellWord')){m.removeAll();m.add({title:'spellchecker.wait','class':'mceMenuItemTitle'}).setDisabled(1);t._sendRPC('getSuggestions',[t.selectedLang,dom.decode(e.target.innerHTML)],function(r){m.removeAll();if(r.length>0){m.add({title:'spellchecker.sug','class':'mceMenuItemTitle'}).setDisabled(1);each(r,function(v){m.add({title:v,onclick:function(){dom.replace(ed.getDoc().createTextNode(v),e.target);t._checkDone();}});});m.addSeparator();}else m.add({title:'spellchecker.no_sug','class':'mceMenuItemTitle'}).setDisabled(1);m.add({title:'spellchecker.ignore_word',onclick:function(){dom.remove(e.target,1);t._checkDone();}});m.add({title:'spellchecker.ignore_words',onclick:function(){t._removeWords(dom.decode(e.target.innerHTML));t._checkDone();}});m.update();});ed.selection.select(e.target);p1=dom.getPos(e.target);m.showMenu(p1.x,p1.y+e.target.offsetHeight-vp.y);return tinymce.dom.Event.cancel(e);}else m.hideMenu();},_checkDone:function(){var t=this,ed=t.editor,dom=ed.dom,o;each(dom.select('span'),function(n){if(n&&dom.hasClass(n,'mceItemHiddenSpellWord')){o=true;return false;}});if(!o)t._done();},_done:function(){var t=this,la=t.active;if(t.active){t.active=0;t._removeWords();if(t._menu)t._menu.hideMenu();if(la)t.editor.nodeChanged();}},_sendRPC:function(m,p,cb){var t=this,url=t.editor.getParam("spellchecker_rpc_url","{backend}");if(url=='{backend}'){t.editor.setProgressState(0);alert('Please specify: spellchecker_rpc_url');return;}JSONRequest.sendRPC({url:url,method:m,params:p,success:cb,error:function(e,x){t.editor.setProgressState(0);t.editor.windowManager.alert(e.errstr||('Error response: '+x.responseText));}});}});tinymce.PluginManager.add('spellchecker',tinymce.plugins.SpellcheckerPlugin);})(); \ No newline at end of file +(function(){var JSONRequest=tinymce.util.JSONRequest,each=tinymce.each,DOM=tinymce.DOM;tinymce.create('tinymce.plugins.SpellcheckerPlugin',{getInfo:function(){return{longname:'Spellchecker',author:'Moxiecode Systems AB',authorurl:'http://tinymce.moxiecode.com',infourl:'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker',version:"2.0.2"};},init:function(ed,url){var t=this,cm;t.url=url;t.editor=ed;ed.addCommand('mceSpellCheck',function(){if(!t.active){ed.setProgressState(1);t._sendRPC('checkWords',[t.selectedLang,t._getWords()],function(r){if(r.length>0){t.active=1;t._markWords(r);ed.setProgressState(0);ed.nodeChanged();}else{ed.setProgressState(0);ed.windowManager.alert('spellchecker.no_mpell');}});}else t._done();});ed.onInit.add(function(){if(ed.settings.content_css!==false)ed.dom.loadCSS(url+'/css/content.css');});ed.onClick.add(t._showMenu,t);ed.onContextMenu.add(t._showMenu,t);ed.onBeforeGetContent.add(function(){if(t.active)t._removeWords();});ed.onNodeChange.add(function(ed,cm){cm.setActive('spellchecker',t.active);});ed.onSetContent.add(function(){t._done();});ed.onBeforeGetContent.add(function(){t._done();});ed.onBeforeExecCommand.add(function(ed,cmd){if(cmd=='mceFullScreen')t._done();});t.languages={};each(ed.getParam('spellchecker_languages','+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv','hash'),function(v,k){if(k.indexOf('+')===0){k=k.substring(1);t.selectedLang=v;}t.languages[k]=v;});},createControl:function(n,cm){var t=this,c,ed=t.editor;if(n=='spellchecker'){c=cm.createSplitButton(n,{title:'spellchecker.desc',cmd:'mceSpellCheck',scope:t});c.onRenderMenu.add(function(c,m){m.add({title:'spellchecker.langs','class':'mceMenuItemTitle'}).setDisabled(1);each(t.languages,function(v,k){var o={icon:1},mi;o.onclick=function(){mi.setSelected(1);t.selectedItem.setSelected(0);t.selectedItem=mi;t.selectedLang=v;};o.title=k;mi=m.add(o);mi.setSelected(v==t.selectedLang);if(v==t.selectedLang)t.selectedItem=mi;})});return c;}},_walk:function(n,f){var d=this.editor.getDoc(),w;if(d.createTreeWalker){w=d.createTreeWalker(n,NodeFilter.SHOW_TEXT,null,false);while((n=w.nextNode())!=null)f.call(this,n);}else tinymce.walk(n,f,'childNodes');},_getSeparators:function(){var re='',i,str=this.editor.getParam('spellchecker_word_separator_chars','\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}����������������\u201d\u201c');for(i=0;i$1$2');v=v.replace(r3,'$1$2');dom.replace(dom.create('span',{'class':'mceItemHidden'},v),n);}}});se.moveToBookmark(b);},_showMenu:function(ed,e){var t=this,ed=t.editor,m=t._menu,p1,dom=ed.dom,vp=dom.getViewPort(ed.getWin());if(!m){p1=DOM.getPos(ed.getContentAreaContainer());m=ed.controlManager.createDropMenu('spellcheckermenu',{offset_x:p1.x,offset_y:p1.y,'class':'mceNoIcons'});t._menu=m;}if(dom.hasClass(e.target,'mceItemHiddenSpellWord')){m.removeAll();m.add({title:'spellchecker.wait','class':'mceMenuItemTitle'}).setDisabled(1);t._sendRPC('getSuggestions',[t.selectedLang,dom.decode(e.target.innerHTML)],function(r){m.removeAll();if(r.length>0){m.add({title:'spellchecker.sug','class':'mceMenuItemTitle'}).setDisabled(1);each(r,function(v){m.add({title:v,onclick:function(){dom.replace(ed.getDoc().createTextNode(v),e.target);t._checkDone();}});});m.addSeparator();}else m.add({title:'spellchecker.no_sug','class':'mceMenuItemTitle'}).setDisabled(1);m.add({title:'spellchecker.ignore_word',onclick:function(){dom.remove(e.target,1);t._checkDone();}});m.add({title:'spellchecker.ignore_words',onclick:function(){t._removeWords(dom.decode(e.target.innerHTML));t._checkDone();}});m.update();});ed.selection.select(e.target);p1=dom.getPos(e.target);m.showMenu(p1.x,p1.y+e.target.offsetHeight-vp.y);return tinymce.dom.Event.cancel(e);}else m.hideMenu();},_checkDone:function(){var t=this,ed=t.editor,dom=ed.dom,o;each(dom.select('span'),function(n){if(n&&dom.hasClass(n,'mceItemHiddenSpellWord')){o=true;return false;}});if(!o)t._done();},_done:function(){var t=this,la=t.active;if(t.active){t.active=0;t._removeWords();if(t._menu)t._menu.hideMenu();if(la)t.editor.nodeChanged();}},_sendRPC:function(m,p,cb){var t=this,url=t.editor.getParam("spellchecker_rpc_url",this.url+'/rpc.php');if(url=='{backend}'){t.editor.setProgressState(0);alert('Please specify: spellchecker_rpc_url');return;}JSONRequest.sendRPC({url:url,method:m,params:p,success:cb,error:function(e,x){t.editor.setProgressState(0);t.editor.windowManager.alert(e.errstr||('Error response: '+x.responseText));}});}});tinymce.PluginManager.add('spellchecker',tinymce.plugins.SpellcheckerPlugin);})(); \ No newline at end of file diff --git a/program/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js b/program/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js index c913c4603..84a9989f6 100644 --- a/program/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js +++ b/program/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js @@ -15,7 +15,7 @@ author : 'Moxiecode Systems AB', authorurl : 'http://tinymce.moxiecode.com', infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker', - version : tinymce.majorVersion + "." + tinymce.minorVersion + version : "2.0.2" }; }, @@ -312,7 +312,7 @@ }, _sendRPC : function(m, p, cb) { - var t = this, url = t.editor.getParam("spellchecker_rpc_url", "{backend}"); + var t = this, url = t.editor.getParam("spellchecker_rpc_url", this.url+'/rpc.php'); if (url == '{backend}') { t.editor.setProgressState(0); diff --git a/program/js/tiny_mce/plugins/spellchecker/includes/general.php b/program/js/tiny_mce/plugins/spellchecker/includes/general.php new file mode 100755 index 000000000..9a1214569 --- /dev/null +++ b/program/js/tiny_mce/plugins/spellchecker/includes/general.php @@ -0,0 +1,98 @@ + $value) + $newarray[formatParam($name, $sanitize)] = formatParam($value, $sanitize); + + return $newarray; + } + + return formatParam($_REQUEST[$name], $sanitize); +} + +function &getLogger() { + global $mcLogger, $man; + + if (isset($man)) + $mcLogger = $man->getLogger(); + + if (!$mcLogger) { + $mcLogger = new Moxiecode_Logger(); + + // Set logger options + $mcLogger->setPath(dirname(__FILE__) . "/../logs"); + $mcLogger->setMaxSize("100kb"); + $mcLogger->setMaxFiles("10"); + $mcLogger->setFormat("{time} - {message}"); + } + + return $mcLogger; +} + +function debug($msg) { + $args = func_get_args(); + + $log = getLogger(); + $log->debug(implode(', ', $args)); +} + +function info($msg) { + $args = func_get_args(); + + $log = getLogger(); + $log->info(implode(', ', $args)); +} + +function error($msg) { + $args = func_get_args(); + + $log = getLogger(); + $log->error(implode(', ', $args)); +} + +function warn($msg) { + $args = func_get_args(); + + $log = getLogger(); + $log->warn(implode(', ', $args)); +} + +function fatal($msg) { + $args = func_get_args(); + + $log = getLogger(); + $log->fatal(implode(', ', $args)); +} + +?> \ No newline at end of file diff --git a/program/js/tiny_mce/plugins/spellchecker/rpc.php b/program/js/tiny_mce/plugins/spellchecker/rpc.php new file mode 100755 index 000000000..a0072ae9c --- /dev/null +++ b/program/js/tiny_mce/plugins/spellchecker/rpc.php @@ -0,0 +1,111 @@ +decode($raw); + +// Execute RPC +if (isset($config['general.engine'])) { + $spellchecker = new $config['general.engine']($config); + $result = call_user_func_array(array($spellchecker, $input['method']), $input['params']); +} else + die('{"result":null,"id":null,"error":{"errstr":"You must choose an spellchecker engine in the config.php file.","errfile":"","errline":null,"errcontext":"","level":"FATAL"}}'); + +// Request and response id should always be the same +$output = array( + "id" => $input->id, + "result" => $result, + "error" => null +); + +// Return JSON encoded string +echo $json->encode($output); + +?> \ No newline at end of file diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 8e5a7c2ef..06e9d0332 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -382,7 +382,7 @@ function rcmail_compose_body($attrib) $OUTPUT->include_script('tiny_mce/tiny_mce.js'); $OUTPUT->include_script("editor.js"); - $OUTPUT->add_script('rcmail_editor_init("$__skin_path", "'.$tinylang.'");'); + $OUTPUT->add_script('rcmail_editor_init("$__skin_path", "'.JQ($tinylang).'", '.intval($CONFIG['enable_spellcheck']).');'); $out = $form_start ? "$form_start\n" : ''; @@ -402,11 +402,13 @@ function rcmail_compose_body($attrib) $out .= $form_end ? "\n$form_end" : ''; // include GoogieSpell - if (!empty($CONFIG['enable_spellcheck']) && !$isHtml) - { - $lang_set = ''; - if (!empty($CONFIG['spellcheck_languages']) && is_array($CONFIG['spellcheck_languages'])) - $lang_set = "googie.setLanguages(".array2js($CONFIG['spellcheck_languages']).");\n"; + if (!empty($CONFIG['enable_spellcheck'])) { + $googie_lang_set = $editor_lang_set = ''; + if (!empty($CONFIG['spellcheck_languages']) && is_array($CONFIG['spellcheck_languages'])) { + $googie_lang_set = "googie.setLanguages(".json_serialize($CONFIG['spellcheck_languages']).");\n"; + foreach ($CONFIG['spellcheck_languages'] as $key => $name) + $editor_lang_set .= ($editor_lang_set ? ',' : '') . ($key == $tinylang ? '+' : '') . JQ($name).'='.JQ($key); + } $OUTPUT->include_script('googiespell.js'); $OUTPUT->add_script(sprintf( @@ -425,13 +427,14 @@ function rcmail_compose_body($attrib) JQ(Q(rcube_label('close'))), JQ(Q(rcube_label('revertto'))), JQ(Q(rcube_label('nospellerrors'))), - $lang_set, + $googie_lang_set, substr($_SESSION['language'], 0, 2), $attrib['id'], JS_OBJECT_NAME), 'foot'); rcube_add_label('checking'); - } + $OUTPUT->set_env('spellcheck_langs', $editor_lang_set); + } $out .= "\n".'';