From 4ca10b8d511d85a4d575af355b0a6739d5a05958 Mon Sep 17 00:00:00 2001
From: thomascube <thomas@roundcube.net>
Date: Fri, 15 Aug 2008 17:47:31 -0400
Subject: [PATCH] Enable spellchecker for HTML editor

---
 CHANGELOG                                                         |    1 
 program/js/tiny_mce/plugins/spellchecker/classes/utils/JSON.php   |  595 ++++++++++++++++++++
 program/js/tiny_mce/plugins/spellchecker/classes/utils/Logger.php |  268 +++++++++
 program/js/tiny_mce/plugins/spellchecker/classes/GoogleSpell.php  |  158 +++++
 program/js/tiny_mce/plugins/spellchecker/includes/general.php     |   98 +++
 program/steps/mail/compose.inc                                    |   19 
 program/js/editor.js                                              |   46 
 program/js/tiny_mce/plugins/spellchecker/classes/EnchantSpell.php |   66 ++
 program/js/tiny_mce/plugins/spellchecker/classes/SpellChecker.php |   61 ++
 program/js/tiny_mce/plugins/spellchecker/config.php               |   33 +
 program/js/tiny_mce/plugins/spellchecker/editor_plugin.js         |    2 
 program/js/tiny_mce/plugins/spellchecker/classes/PSpell.php       |   81 ++
 program/js/tiny_mce/plugins/spellchecker/rpc.php                  |  111 +++
 program/js/tiny_mce/plugins/spellchecker/classes/PSpellShell.php  |  112 +++
 program/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js     |    4 
 bin/html2text.php                                                 |    2 
 program/js/app.js                                                 |   31 
 17 files changed, 1,643 insertions(+), 45 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 4a01686..d42fca9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,7 @@
 - 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 0f0e6ae..2650bf2 100644
--- a/bin/html2text.php
+++ b/bin/html2text.php
@@ -6,6 +6,6 @@
 $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 998efd7..c82ebf9 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -195,6 +195,8 @@
             {
             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 @@
           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 @@
         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 @@
       }
 
     // 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 @@
     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 6fdbadc..38c9b71 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 @@
     composeElement.value = htmlText;
     tinyMCE.execCommand('mceAddControl', true, 'compose-body');
     htmlFlag.value = "1";
+    rcmail.display_spellcheck_controls(false);
     }
   else
     {
@@ -72,6 +75,7 @@
     rcmail_html2plain(existingHtml);
     tinyMCE.execCommand('mceRemoveControl', true, 'compose-body');
     htmlFlag.value = "0";
+    rcmail.display_spellcheck_controls(true);
     }
   }
 
@@ -82,7 +86,7 @@
   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 0000000..fb6e67c
--- /dev/null
+++ b/program/js/tiny_mce/plugins/spellchecker/classes/EnchantSpell.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * This class was contributed by Michel Weimerskirch.
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2007, Moxiecode Systems AB, All rights reserved.
+ */
+
+class EnchantSpell extends SpellChecker {
+	/**
+	 * Spellchecks an array of words.
+	 *
+	 * @param String $lang Selected language code (like en_US or de_DE). Shortcodes like "en" and "de" work with enchant >= 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 0000000..53d4300
--- /dev/null
+++ b/program/js/tiny_mce/plugins/spellchecker/classes/GoogleSpell.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2007, Moxiecode Systems AB, All rights reserved.
+ */
+
+class GoogleSpell extends SpellChecker {
+	/**
+	 * 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) {
+		$wordstr = implode(' ', $words);
+		$matches = $this->_getMatches($lang, $wordstr);
+		$words = array();
+
+		for ($i=0; $i<count($matches); $i++)
+			$words[] = $this->_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 = '<?xml version="1.0" encoding="utf-8" ?><spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1"><text>' . $str . '</text></spellrequest>';
+
+		$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 o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/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 0000000..45448d0
--- /dev/null
+++ b/program/js/tiny_mce/plugins/spellchecker/classes/PSpell.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2007, Moxiecode Systems AB, All rights reserved.
+ */
+
+class PSpell extends SpellChecker {
+	/**
+	 * 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) {
+		$plink = $this->_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; $i<count($words); $i++)
+			$words[$i] = utf8_encode($words[$i]);
+
+		return $words;
+	}
+
+	/**
+	 * Opens a link for pspell.
+	 */
+	function &_getPLink($lang) {
+		// Check for native PSpell support
+		if (!function_exists("pspell_new"))
+			$this->throwError("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 0000000..0bc57de
--- /dev/null
+++ b/program/js/tiny_mce/plugins/spellchecker/classes/PSpellShell.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2007, Moxiecode Systems AB, All rights reserved.
+ */
+
+class PSpellShell extends SpellChecker {
+	/**
+	 * 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) {
+		$cmd = $this->_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<count($words); $i++)
+					$words[$i] = trim($words[$i]);
+
+				return $words;
+			}
+		}
+
+		return array();
+	}
+
+	function _getCMD($lang) {
+		$this->_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 0000000..d680039
--- /dev/null
+++ b/program/js/tiny_mce/plugins/spellchecker/classes/SpellChecker.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2007, Moxiecode Systems AB, All rights reserved.
+ */
+
+class SpellChecker {
+	/**
+	 * Constructor.
+	 *
+	 * @param $config Configuration name/value array.
+	 */
+	function SpellChecker(&$config) {
+		$this->_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 0000000..1c46116
--- /dev/null
+++ b/program/js/tiny_mce/plugins/spellchecker/classes/utils/JSON.php
@@ -0,0 +1,595 @@
+<?php
+/**
+ * $Id: JSON.php 40 2007-06-18 11:43:15Z spocke $
+ *
+ * @package MCManager.utils
+ * @author Moxiecode
+ * @copyright Copyright � 2007, Moxiecode Systems AB, All rights reserved.
+ */
+
+define('JSON_BOOL', 1);
+define('JSON_INT', 2);
+define('JSON_STR', 3);
+define('JSON_FLOAT', 4);
+define('JSON_NULL', 5);
+define('JSON_START_OBJ', 6);
+define('JSON_END_OBJ', 7);
+define('JSON_START_ARRAY', 8);
+define('JSON_END_ARRAY', 9);
+define('JSON_KEY', 10);
+define('JSON_SKIP', 11);
+
+define('JSON_IN_ARRAY', 30);
+define('JSON_IN_OBJECT', 40);
+define('JSON_IN_BETWEEN', 50);
+
+class Moxiecode_JSONReader {
+	var $_data, $_len, $_pos;
+	var $_value, $_token;
+	var $_location, $_lastLocations;
+	var $_needProp;
+
+	function Moxiecode_JSONReader($data) {
+		$this->_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<strlen($input); $i++) {
+				switch ($input[$i]) {
+					case "\b":
+						$output .= "\\b";
+						break;
+
+					case "\t":
+						$output .= "\\t";
+						break;
+
+					case "\f":
+						$output .= "\\f";
+						break;
+
+					case "\r":
+						$output .= "\\r";
+						break;
+
+					case "\n":
+						$output .= "\\n";
+						break;
+
+					case '\\':
+						$output .= "\\\\";
+						break;
+
+					case '\'':
+						$output .= "\\'";
+						break;
+
+					case '"':
+						$output .= '\"';
+						break;
+
+					default:
+						$byte = ord($input[$i]);
+
+						if (($byte & 0xE0) == 0xC0) {
+							$char = pack('C*', $byte, ord($input[$i + 1]));
+							$i += 1;
+							$output .= sprintf('\u%04s', bin2hex($this->_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; $i<count($keys); $i++) {
+			if (!is_int($keys[$i])) {
+				$output .= $this->encodeString($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 0000000..bc501ea
--- /dev/null
+++ b/program/js/tiny_mce/plugins/spellchecker/classes/utils/Logger.php
@@ -0,0 +1,268 @@
+<?php
+/**
+ * $Id: Logger.class.php 10 2007-05-27 10:55:12Z spocke $
+ *
+ * @package MCFileManager.filesystems
+ * @author Moxiecode
+ * @copyright Copyright � 2005, Moxiecode Systems AB, All rights reserved.
+ */
+
+// File type contstants
+define('MC_LOGGER_DEBUG', 0);
+define('MC_LOGGER_INFO', 10);
+define('MC_LOGGER_WARN', 20);
+define('MC_LOGGER_ERROR', 30);
+define('MC_LOGGER_FATAL', 40);
+
+/**
+ * Logging utility class. This class handles basic logging with levels, log rotation and custom log formats. It's
+ * designed to be compact but still powerful and flexible.
+ */
+class Moxiecode_Logger {
+	// Private fields
+	var $_path;
+	var $_filename;
+	var $_maxSize;
+	var $_maxFiles;
+	var $_maxSizeBytes;
+	var $_level;
+	var $_format;
+
+	/**
+	 * Constructs a new logger instance.
+	 */
+	function Moxiecode_Logger() {
+		$this->_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 0000000..827749e
--- /dev/null
+++ b/program/js/tiny_mce/plugins/spellchecker/config.php
@@ -0,0 +1,33 @@
+<?php
+
+	/** start RoundCube specific code */
+	
+	define('INSTALL_PATH', preg_replace('/program\/js\/.+$/', '', getcwd()));
+	require_once INSTALL_PATH . 'program/include/iniset.php';
+	
+	$rcmail_config = new rcube_config();
+	$config['general.engine'] = $rcmail_config->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 9cb6799..915fc40 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<str.length;i++)re+='\\'+str.charAt(i);return re;},_getWords:function(){var ed=this.editor,wl=[],tx='',lo={};this._walk(ed.getBody(),function(n){if(n.nodeType==3)tx+=n.nodeValue+' ';});tx=tx.replace(new RegExp('([0-9]|['+this._getSeparators()+'])','g'),' ');tx=tinymce.trim(tx.replace(/(\s+)/g,' '));each(tx.split(' '),function(v){if(!lo[v]){wl.push(v);lo[v]=1;}});return wl;},_removeWords:function(w){var ed=this.editor,dom=ed.dom,se=ed.selection,b=se.getBookmark();each(dom.select('span').reverse(),function(n){if(n&&(dom.hasClass(n,'mceItemHiddenSpellWord')||dom.hasClass(n,'mceItemHidden'))){if(!w||dom.decode(n.innerHTML)==w)dom.remove(n,1);}});se.moveToBookmark(b);},_markWords:function(wl){var r1,r2,r3,r4,r5,w='',ed=this.editor,re=this._getSeparators(),dom=ed.dom,nl=[];var se=ed.selection,b=se.getBookmark();each(wl,function(v){w+=(w?'|':'')+v;});r1=new RegExp('(['+re+'])('+w+')(['+re+'])','g');r2=new RegExp('^('+w+')','g');r3=new RegExp('('+w+')(['+re+']?)$','g');r4=new RegExp('^('+w+')(['+re+']?)$','g');r5=new RegExp('('+w+')(['+re+'])','g');this._walk(this.editor.getBody(),function(n){if(n.nodeType==3){nl.push(n);}});each(nl,function(n){var v;if(n.nodeType==3){v=n.nodeValue;if(r1.test(v)||r2.test(v)||r3.test(v)||r4.test(v)){v=dom.encode(v);v=v.replace(r5,'<span class="mceItemHiddenSpellWord">$1</span>$2');v=v.replace(r3,'<span class="mceItemHiddenSpellWord">$1</span>$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<str.length;i++)re+='\\'+str.charAt(i);return re;},_getWords:function(){var ed=this.editor,wl=[],tx='',lo={};this._walk(ed.getBody(),function(n){if(n.nodeType==3)tx+=n.nodeValue+' ';});tx=tx.replace(new RegExp('([0-9]|['+this._getSeparators()+'])','g'),' ');tx=tinymce.trim(tx.replace(/(\s+)/g,' '));each(tx.split(' '),function(v){if(!lo[v]){wl.push(v);lo[v]=1;}});return wl;},_removeWords:function(w){var ed=this.editor,dom=ed.dom,se=ed.selection,b=se.getBookmark();each(dom.select('span').reverse(),function(n){if(n&&(dom.hasClass(n,'mceItemHiddenSpellWord')||dom.hasClass(n,'mceItemHidden'))){if(!w||dom.decode(n.innerHTML)==w)dom.remove(n,1);}});se.moveToBookmark(b);},_markWords:function(wl){var r1,r2,r3,r4,r5,w='',ed=this.editor,re=this._getSeparators(),dom=ed.dom,nl=[];var se=ed.selection,b=se.getBookmark();each(wl,function(v){w+=(w?'|':'')+v;});r1=new RegExp('(['+re+'])('+w+')(['+re+'])','g');r2=new RegExp('^('+w+')','g');r3=new RegExp('('+w+')(['+re+']?)$','g');r4=new RegExp('^('+w+')(['+re+']?)$','g');r5=new RegExp('('+w+')(['+re+'])','g');this._walk(this.editor.getBody(),function(n){if(n.nodeType==3){nl.push(n);}});each(nl,function(n){var v;if(n.nodeType==3){v=n.nodeValue;if(r1.test(v)||r2.test(v)||r3.test(v)||r4.test(v)){v=dom.encode(v);v=v.replace(r5,'<span class="mceItemHiddenSpellWord">$1</span>$2');v=v.replace(r3,'<span class="mceItemHiddenSpellWord">$1</span>$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 c913c46..84a9989 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 0000000..9a12145
--- /dev/null
+++ b/program/js/tiny_mce/plugins/spellchecker/includes/general.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * general.php
+ *
+ * @package MCManager.includes
+ * @author Moxiecode
+ * @copyright Copyright � 2007, Moxiecode Systems AB, All rights reserved.
+ */
+
+@error_reporting(E_ALL ^ E_NOTICE);
+$config = array();
+
+require_once(dirname(__FILE__) . "/../classes/utils/Logger.php");
+require_once(dirname(__FILE__) . "/../classes/utils/JSON.php");
+require_once(dirname(__FILE__) . "/../config.php");
+require_once(dirname(__FILE__) . "/../classes/SpellChecker.php");
+
+if (isset($config['general.engine']))
+	require_once(dirname(__FILE__) . "/../classes/" . $config["general.engine"] . ".php");
+
+/**
+ * Returns an request value by name without magic quoting.
+ *
+ * @param String $name Name of parameter to get.
+ * @param String $default_value Default value to return if value not found.
+ * @return String request value by name without magic quoting or default value.
+ */
+function getRequestParam($name, $default_value = false, $sanitize = false) {
+	if (!isset($_REQUEST[$name]))
+		return $default_value;
+
+	if (is_array($_REQUEST[$name])) {
+		$newarray = array();
+
+		foreach ($_REQUEST[$name] as $name => $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 0000000..a0072ae
--- /dev/null
+++ b/program/js/tiny_mce/plugins/spellchecker/rpc.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * $Id: rpc.php 822 2008-04-28 13:45:03Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2007, Moxiecode Systems AB, All rights reserved.
+ */
+
+require_once("./includes/general.php");
+
+// Set RPC response headers
+header('Content-Type: text/plain');
+header('Content-Encoding: UTF-8');
+header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
+header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
+header("Cache-Control: no-store, no-cache, must-revalidate");
+header("Cache-Control: post-check=0, pre-check=0", false);
+header("Pragma: no-cache");
+
+$raw = "";
+
+// Try param
+if (isset($_POST["json_data"]))
+	$raw = getRequestParam("json_data");
+
+// Try globals array
+if (!$raw && isset($_GLOBALS) && isset($_GLOBALS["HTTP_RAW_POST_DATA"]))
+	$raw = $_GLOBALS["HTTP_RAW_POST_DATA"];
+
+// Try globals variable
+if (!$raw && isset($HTTP_RAW_POST_DATA))
+	$raw = $HTTP_RAW_POST_DATA;
+
+// Try stream
+if (!$raw) {
+	if (!function_exists('file_get_contents')) {
+		$fp = fopen("php://input", "r");
+		if ($fp) {
+			$raw = "";
+
+			while (!feof($fp))
+				$raw = fread($fp, 1024);
+
+			fclose($fp);
+		}
+	} else
+		$raw = "" . file_get_contents("php://input");
+}
+
+// No input data
+if (!$raw)
+	die('{"result":null,"id":null,"error":{"errstr":"Could not get raw post data.","errfile":"","errline":null,"errcontext":"","level":"FATAL"}}');
+
+// Passthrough request to remote server
+if (isset($config['general.remote_rpc_url'])) {
+	$url = parse_url($config['general.remote_rpc_url']);
+
+	// Setup request
+	$req = "POST " . $url["path"] . " HTTP/1.0\r\n";
+	$req .= "Connection: close\r\n";
+	$req .= "Host: " . $url['host'] . "\r\n";
+	$req .= "Content-Length: " . strlen($raw) . "\r\n";
+	$req .= "\r\n" . $raw;
+
+	if (!isset($url['port']) || !$url['port'])
+		$url['port'] = 80;
+
+	$errno = $errstr = "";
+
+	$socket = fsockopen($url['host'], intval($url['port']), $errno, $errstr, 30);
+	if ($socket) {
+		// Send request headers
+		fputs($socket, $req);
+
+		// Read response headers and data
+		$resp = "";
+		while (!feof($socket))
+				$resp .= fgets($socket, 4096);
+
+		fclose($socket);
+
+		// Split response header/data
+		$resp = explode("\r\n\r\n", $resp);
+		echo $resp[1]; // Output body
+	}
+
+	die();
+}
+
+// Get JSON data
+$json = new Moxiecode_JSON();
+$input = $json->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 8e5a7c2..06e9d03 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -382,7 +382,7 @@
 
   $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 @@
   $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 @@
       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".'<iframe name="savetarget" src="program/blank.gif" style="width:0;height:0;border:none;visibility:hidden;"></iframe>';
 

--
Gitblit v1.9.1