svncommit
2006-09-23 6649b1f0a5db6160d197a13ca79cfd67fbb02d77
added TinyMCE spellchecker plugin, configured to use GoogleSpell



13 files added
1112 ■■■■■ changed files
program/js/tiny_mce/plugins/spellchecker/changelog 11 ●●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/classes/TinyGoogleSpell.class.php 107 ●●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/classes/TinyPspell.class.php 64 ●●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/classes/TinyPspellShell.class.php 121 ●●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/config.php 24 ●●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/css/content.css 5 ●●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/css/spellchecker.css 34 ●●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/editor_plugin.js 1 ●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js 588 ●●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/images/spellchecker.gif patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/images/wline.gif patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/langs/en.js 15 ●●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/tinyspell.php 142 ●●●●● patch | view | raw | blame | history
program/js/tiny_mce/plugins/spellchecker/changelog
New file
@@ -0,0 +1,11 @@
Version 1.0.2 (2006-08-02)
    Added new spellchecker_report_mispellings option, contributed by Jeremy B.
    Fixed various regexp bugs and issues. Some where contributed by Jeremy B.
    Fixed the Google speller class so it uses curl, patch contributed by Yuriy Kramar.
    Fixed encoding issues with language specific characters, patch contributed by codepit.
    Fixed bug where the spellchecker wasn't working in MSIE if the editor was placed in a P tag.
Version 1.0.1 (2006-05-05)
    Since sourceforge has a serious bug when it comes to replacing files with the same name this release was necessary.
    Goggle spellchecker class was added.
Version 1.0 (2006-05-03)
    Official first release.
program/js/tiny_mce/plugins/spellchecker/classes/TinyGoogleSpell.class.php
New file
@@ -0,0 +1,107 @@
<?php
/* *
 * Tiny Spelling Interface for TinyMCE Spell Checking.
 *
 * Copyright © 2006 Moxiecode Systems AB
 */
class TinyGoogleSpell {
    var $lang;
    function TinyGoogleSpell(&$config, $lang, $mode, $spelling, $jargon, $encoding) {
        $this->lang = $lang;
    }
    // Returns array with bad words or false if failed.
    function checkWords($word_array) {
        $words = array();
        $wordstr = implode(' ', $word_array);
        $matches = $this->_getMatches($wordstr);
        for ($i=0; $i<count($matches); $i++)
            $words[] = $this->unhtmlentities(mb_substr($wordstr, $matches[$i][1], $matches[$i][2], "UTF-8"));
        return $words;
    }
    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);
    }
    // Returns array with suggestions or false if failed.
    function getSuggestion($word) {
        $sug = array();
        $matches = $this->_getMatches($word);
        if (count($matches) > 0)
            $sug = explode("\t", utf8_encode($this->unhtmlentities($matches[0][4])));
        return $sug;
    }
    function _xmlChars($string) {
       $trans = get_html_translation_table(HTML_ENTITIES, ENT_QUOTES);
       foreach ($trans as $k => $v)
            $trans[$k] = "&#".ord($k).";";
       return strtr($string, $trans);
    }
    function _getMatches($word_list) {
        $server = "www.google.com";
        $port = 443;
        $path = "/tbproxy/spell?lang=" . $this->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>' . $word_list . '</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;
        //$this->_debugData($xml);
        $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);
        //$this->_debugData($xml);
        // Grab and parse content
        preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $xml, $matches, PREG_SET_ORDER);
        return $matches;
    }
    function _debugData($data) {
        $fh = @fopen("debug.log", 'a+');
        @fwrite($fh, $data);
        @fclose($fh);
    }
}
// Setup classname, should be the same as the name of the spellchecker class
$spellCheckerConfig['class'] = "TinyGoogleSpell";
?>
program/js/tiny_mce/plugins/spellchecker/classes/TinyPspell.class.php
New file
@@ -0,0 +1,64 @@
<?php
/* *
 * Tiny Spelling Interface for TinyMCE Spell Checking.
 *
 * Copyright © 2006 Moxiecode Systems AB
 *
 */
class TinyPSpell {
    var $lang;
    var $mode;
    var $string;
    var $plink;
    var $errorMsg;
    var $jargon;
    var $spelling;
    var $encoding;
    function TinyPSpell(&$config, $lang, $mode, $spelling, $jargon, $encoding) {
        $this->lang = $lang;
        $this->mode = $mode;
        $this->plink = false;
        $this->errorMsg = array();
        if (!function_exists("pspell_new")) {
            $this->errorMsg[] = "PSpell not found.";
            return;
        }
        $this->plink = pspell_new($this->lang, $this->spelling, $this->jargon, $this->encoding, $this->mode);
    }
    // Returns array with bad words or false if failed.
    function checkWords($wordArray) {
        if (!$this->plink) {
            $this->errorMsg[] = "No PSpell link found for checkWords.";
            return array();
        }
        $wordError = array();
        foreach($wordArray as $word) {
            if(!pspell_check($this->plink, trim($word)))
                $wordError[] = $word;
        }
        return $wordError;
    }
    // Returns array with suggestions or false if failed.
    function getSuggestion($word) {
        if (!$this->plink) {
            $this->errorMsg[] = "No PSpell link found for getSuggestion.";
            return array();
        }
        return pspell_suggest($this->plink, $word);
    }
}
// Setup classname, should be the same as the name of the spellchecker class
$spellCheckerConfig['class'] = "TinyPspell";
?>
program/js/tiny_mce/plugins/spellchecker/classes/TinyPspellShell.class.php
New file
@@ -0,0 +1,121 @@
<?php
/* *
 * Tiny Spelling Interface for TinyMCE Spell Checking.
 *
 * Copyright © 2006 Moxiecode Systems AB
 *
 */
class TinyPspellShell {
    var $lang;
    var $mode;
    var $string;
    var $error;
    var $errorMsg;
    var $cmd;
    var $tmpfile;
    var $jargon;
    var $spelling;
    var $encoding;
    function TinyPspellShell(&$config, $lang, $mode, $spelling, $jargon, $encoding) {
        $this->lang = $lang;
        $this->mode = $mode;
        $this->error = false;
        $this->errorMsg = array();
        $this->tmpfile = tempnam($config['tinypspellshell.tmp'], "tinyspell");
        if(preg_match("#win#i",php_uname()))
            $this->cmd = $config['tinypspellshell.aspell'] . " -a --lang=". $this->lang." --encoding=utf-8 -H < $this->tmpfile 2>&1";
        else
            $this->cmd = "cat ". $this->tmpfile ." | " . $config['tinypspellshell.aspell'] . " -a --encoding=utf-8 -H --lang=". $this->lang;
    }
    // Returns array with bad words or false if failed.
    function checkWords($wordArray) {
        if ($fh = fopen($this->tmpfile, "w")) {
            fwrite($fh, "!\n");
            foreach($wordArray as $key => $value)
                fwrite($fh, "^" . $value . "\n");
            fclose($fh);
        } else {
            $this->errorMsg[] = "PSpell not found.";
            return array();
        }
        $data = shell_exec($this->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]))
                $returnData[] = $matches[1];
        }
        return $returnData;
    }
    // Returns array with suggestions or false if failed.
    function getSuggestion($word) {
        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
            die("Error opening tmp file.");
        $data = shell_exec($this->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])) {
                // For some reason, the exec version seems to add commas?
                $returnData[] = str_replace(",", "", $matches[1]);
            }
        }
        return $returnData;
    }
    function _debugData($data) {
        $fh = @fopen("debug.log", 'a+');
        @fwrite($fh, $data);
        @fclose($fh);
    }
}
// Setup classname, should be the same as the name of the spellchecker class
$spellCheckerConfig['class'] = "TinyPspellShell";
?>
program/js/tiny_mce/plugins/spellchecker/config.php
New file
@@ -0,0 +1,24 @@
<?php
    $spellCheckerConfig = array();
    // Spellchecker class use
    // require_once("classes/TinyPspellShell.class.php"); // Command line pspell
    require_once("classes/TinyGoogleSpell.class.php"); // Google web service
    // require_once("classes/TinyPspell.class.php"); // Internal PHP version
    // General settings
    $spellCheckerConfig['enabled'] = false;
    // Default settings
    $spellCheckerConfig['default.language'] = 'en';
    $spellCheckerConfig['default.mode'] = PSPELL_FAST;
    // Normaly not required to configure
    $spellCheckerConfig['default.spelling'] = "";
    $spellCheckerConfig['default.jargon'] = "";
    $spellCheckerConfig['default.encoding'] = "";
    // Pspell shell specific settings
    $spellCheckerConfig['tinypspellshell.aspell'] = '/usr/bin/aspell';
    $spellCheckerConfig['tinypspellshell.tmp'] = '/tmp';
?>
program/js/tiny_mce/plugins/spellchecker/css/content.css
New file
@@ -0,0 +1,5 @@
.mceItemHiddenSpellWord {
    background: url('../images/wline.gif') repeat-x bottom left;
    bo2rder-bottom: 1px dashed red;
    cursor: default;
}
program/js/tiny_mce/plugins/spellchecker/css/spellchecker.css
New file
@@ -0,0 +1,34 @@
.mceMsgBox {
    border: 1px solid gray;
    padding: 8px;
}
.mceMsgBox span {
    vertical-align: top;
    color: #555555;
}
/* Misc */
.mceBlockBox {
    display: none;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 100;
    filter:progid:DXImageTransform.Microsoft.Alpha(style=0, opacity=60);
    -moz-opacity:0.6;
    opacity: 0.6;
    background-color: white;
}
.mceMsgBox {
    display: none;
    z-index: 101;
    position: absolute;
    left: 0;
    top: 0;
    font-family: Arial, Verdana, Tahoma, Helvetica;
    font-weight: bold;
    font-size: 11px;
}
program/js/tiny_mce/plugins/spellchecker/editor_plugin.js
New file
@@ -0,0 +1 @@
tinyMCE.importPluginLanguagePack('spellchecker','en,fr,sv,nn,nb');var TinyMCE_SpellCheckerPlugin={_contextMenu:new TinyMCE_Menu(),_menu:new TinyMCE_Menu(),_counter:0,_ajaxPage:'/tinyspell.php',getInfo:function(){return{longname:'Spellchecker',author:'Moxiecode Systems AB',authorurl:'http://tinymce.moxiecode.com',infourl:'http://tinymce.moxiecode.com/tinymce/docs/plugin_spellchecker.html',version:tinyMCE.majorVersion+"."+tinyMCE.minorVersion};},handleEvent:function(e){var elm=tinyMCE.isMSIE?e.srcElement:e.target;var inst=tinyMCE.selectedInstance,args='';var self=TinyMCE_SpellCheckerPlugin;var cm=self._contextMenu;var p,p2,x,y,sx,sy,h,elm;if((e.type=="click"||e.type=="contextmenu")&&elm){do{if(tinyMCE.getAttrib(elm,'class')=="mceItemHiddenSpellWord"){inst.spellCheckerElm=elm;args+='id='+inst.editorId+"|"+(++self._counter);args+='&cmd=suggest&check='+encodeURIComponent(elm.innerHTML);args+='&lang='+escape(inst.spellCheckerLang);elm=inst.spellCheckerElm;p=tinyMCE.getAbsPosition(inst.iframeElement);p2=tinyMCE.getAbsPosition(elm);h=parseInt(elm.offsetHeight);sx=inst.getBody().scrollLeft;sy=inst.getBody().scrollTop;x=p.absLeft+p2.absLeft-sx;y=p.absTop+p2.absTop-sy+h;cm.clear();cm.addTitle(tinyMCE.getLang('lang_spellchecker_wait','',true));cm.show();cm.moveTo(x,y);inst.selection.selectNode(elm,false,false);self._sendAjax(self.baseURL+self._ajaxPage,self._ajaxResponse,'post',args);tinyMCE.cancelEvent(e);return false;}}while((elm=elm.parentNode));}return true;},initInstance:function(inst){var self=TinyMCE_SpellCheckerPlugin,m=self._menu,cm=self._contextMenu,e;tinyMCE.importCSS(inst.getDoc(),tinyMCE.baseURL+"/plugins/spellchecker/css/content.css");if(!tinyMCE.hasMenu('spellcheckercontextmenu')){tinyMCE.importCSS(document,tinyMCE.baseURL+"/plugins/spellchecker/css/spellchecker.css");cm.init({drop_menu:false});tinyMCE.addMenu('spellcheckercontextmenu',cm);}if(!tinyMCE.hasMenu('spellcheckermenu')){m.init({});tinyMCE.addMenu('spellcheckermenu',m);}inst.spellCheckerLang='en';self._buildSettingsMenu(inst,null);e=self._getBlockBoxLayer(inst).create('div','mceBlockBox',document.getElementById(inst.editorId+'_parent'));self._getMsgBoxLayer(inst).create('div','mceMsgBox',document.getElementById(inst.editorId+'_parent'));},_getMsgBoxLayer:function(inst){if(!inst.spellCheckerMsgBoxL)inst.spellCheckerMsgBoxL=new TinyMCE_Layer(inst.editorId+'_spellcheckerMsgBox',false);return inst.spellCheckerMsgBoxL;},_getBlockBoxLayer:function(inst){if(!inst.spellCheckerBoxL)inst.spellCheckerBoxL=new TinyMCE_Layer(inst.editorId+'_spellcheckerBlockBox',false);return inst.spellCheckerBoxL;},_buildSettingsMenu:function(inst,lang){var i,ar=tinyMCE.getParam('spellchecker_languages','+English=en').split(','),p;var self=TinyMCE_SpellCheckerPlugin,m=self._menu,c;m.clear();m.addTitle(tinyMCE.getLang('lang_spellchecker_langs','',true));for(i=0;i<ar.length;i++){if(ar[i]!=''){p=ar[i].split('=');c='mceMenuCheckItem';if(p[0].charAt(0)=='+'){p[0]=p[0].substring(1);if(lang==null){c='mceMenuSelectedItem';inst.spellCheckerLang=p[1];}}if(lang==p[1])c='mceMenuSelectedItem';m.add({text:p[0],js:"tinyMCE.execInstanceCommand('"+inst.editorId+"','mceSpellCheckerSetLang',false,'"+p[1]+"');",class_name:c});}}},setupContent:function(editor_id,body,doc){TinyMCE_SpellCheckerPlugin._removeWords(doc);},getControlHTML:function(cn){switch(cn){case"spellchecker":return TinyMCE_SpellCheckerPlugin._getMenuButtonHTML(cn,'lang_spellchecker_desc','{$pluginurl}/images/spellchecker.gif','lang_spellchecker_desc','mceSpellCheckerMenu','mceSpellCheck');}return"";},_getMenuButtonHTML:function(id,lang,img,mlang,mid,cmd,ui,val){var h='',m,x;cmd='tinyMCE.hideMenus();tinyMCE.execInstanceCommand(\'{$editor_id}\',\''+cmd+'\'';if(typeof(ui)!="undefined"&&ui!=null)cmd+=','+ui;if(typeof(val)!="undefined"&&val!=null)cmd+=",'"+val+"'";cmd+=');';if(tinyMCE.getParam('button_tile_map')&&(!tinyMCE.isMSIE||tinyMCE.isOpera)&&(m=tinyMCE.buttonMap[id])!=null&&(tinyMCE.getParam("language")=="en"||img.indexOf('$lang')==-1)){x=0-(m*20)==0?'0':0-(m*20);h+='<a id="{$editor_id}_'+id+'" href="javascript:'+cmd+'" onclick="'+cmd+'return false;" onmousedown="return false;" class="mceTiledButton mceButtonNormal" target="_self">';h+='<img src="{$themeurl}/images/spacer.gif" style="background-position: '+x+'px 0" title="{$'+lang+'}" />';h+='<img src="{$themeurl}/images/button_menu.gif" title="{$'+lang+'}" class="mceMenuButton" onclick="'+mcmd+'return false;" />';h+='</a>';}else{if(tinyMCE.isMSIE&&!tinyMCE.isOpera)h+='<span id="{$editor_id}_'+id+'" class="mceMenuButton" onmouseover="tinyMCE.plugins.spellchecker._menuButtonEvent(\'over\',this);" onmouseout="tinyMCE.plugins.spellchecker._menuButtonEvent(\'out\',this);">';else h+='<span id="{$editor_id}_'+id+'" class="mceMenuButton">';h+='<a href="javascript:'+cmd+'" onclick="'+cmd+'return false;" onmousedown="return false;" class="mceMenuButtonNormal" target="_self">';h+='<img src="'+img+'" title="{$'+lang+'}" /></a>';h+='<a href="#" onclick="tinyMCE.plugins.spellchecker._toggleMenu(\'{$editor_id}\',\''+mid+'\');return false;" onmousedown="return false;"><img src="{$themeurl}/images/button_menu.gif" title="{$'+lang+'}" class="mceMenuButton" />';h+='</a></span>';}return h;},_menuButtonEvent:function(e,o){if(o.className=='mceMenuButtonFocus')return;if(e=='over')o.className=o.className+' mceMenuHover';else o.className=o.className.replace(/\s.*$/,'');},_toggleMenu:function(editor_id,id){var self=TinyMCE_SpellCheckerPlugin;var e=document.getElementById(editor_id+'_spellchecker');var inst=tinyMCE.getInstanceById(editor_id);if(self._menu.isVisible()){tinyMCE.hideMenus();return;}tinyMCE.lastMenuBtnClass=e.className.replace(/\s.*$/,'');tinyMCE.switchClass(editor_id+'_spellchecker','mceMenuButtonFocus');self._menu.moveRelativeTo(e,'bl');self._menu.moveBy(tinyMCE.isMSIE&&!tinyMCE.isOpera?0:1,-1);if(tinyMCE.isOpera)self._menu.moveBy(0,-2);self._onMenuEvent(inst,self._menu,'show');self._menu.show();tinyMCE.lastSelectedMenuBtn=editor_id+'_spellchecker';},_onMenuEvent:function(inst,m,n){TinyMCE_SpellCheckerPlugin._buildSettingsMenu(inst,inst.spellCheckerLang);},execCommand:function(editor_id,element,command,user_interface,value){var inst=tinyMCE.getInstanceById(editor_id),self=TinyMCE_SpellCheckerPlugin,args='',co,bb,mb,nl,i,e;switch(command){case"mceSpellCheck":if(!inst.spellcheckerOn){inst.spellCheckerBookmark=inst.selection.getBookmark();args+='id='+inst.editorId+"|"+(++self._counter);args+='&cmd=spell&check='+encodeURIComponent(self._getWordList(inst.getBody())).replace(/\'/g,'%27');args+='&lang='+escape(inst.spellCheckerLang);co=document.getElementById(inst.editorId+'_parent').firstChild;bb=self._getBlockBoxLayer(inst);bb.moveRelativeTo(co,'tl');bb.resizeTo(co.offsetWidth,co.offsetHeight);bb.show();mb=self._getMsgBoxLayer(inst);e=mb.getElement();e.innerHTML='<span>'+tinyMCE.getLang('lang_spellchecker_swait','',true)+'</span>';mb.show();mb.moveRelativeTo(co,'cc');if(tinyMCE.isMSIE&&!tinyMCE.isOpera){nl=co.getElementsByTagName('select');for(i=0;i<nl.length;i++)nl[i].disabled=true;}inst.spellcheckerOn=true;tinyMCE.switchClass(editor_id+'_spellchecker','mceMenuButtonSelected');self._sendAjax(self.baseURL+self._ajaxPage,self._ajaxResponse,'post',args);}else{self._removeWords(inst.getDoc());inst.spellcheckerOn=false;tinyMCE.switchClass(editor_id+'_spellchecker','mceMenuButton');}return true;case"mceSpellCheckReplace":if(inst.spellCheckerElm)tinyMCE.setOuterHTML(inst.spellCheckerElm,value);self._checkDone(inst);self._contextMenu.hide();self._menu.hide();return true;case"mceSpellCheckIgnore":if(inst.spellCheckerElm)self._removeWord(inst.spellCheckerElm);self._checkDone(inst);self._contextMenu.hide();self._menu.hide();return true;case"mceSpellCheckIgnoreAll":if(inst.spellCheckerElm)self._removeWords(inst.getDoc(),inst.spellCheckerElm.innerHTML);self._checkDone(inst);self._contextMenu.hide();self._menu.hide();return true;case"mceSpellCheckerSetLang":tinyMCE.hideMenus();inst.spellCheckerLang=value;self._removeWords(inst.getDoc());inst.spellcheckerOn=false;tinyMCE.switchClass(editor_id+'_spellchecker','mceMenuButton');return true;}return false;},cleanup:function(type,content,inst){switch(type){case"get_from_editor_dom":TinyMCE_SpellCheckerPlugin._removeWords(content);inst.spellcheckerOn=false;break;}return content;},_displayUI:function(inst){var self=TinyMCE_SpellCheckerPlugin;var bb=self._getBlockBoxLayer(inst);var mb=self._getMsgBoxLayer(inst);var nl,i;var co=document.getElementById(inst.editorId+'_parent').firstChild;if(tinyMCE.isMSIE&&!tinyMCE.isOpera){nl=co.getElementsByTagName('select');for(i=0;i<nl.length;i++)nl[i].disabled=false;}bb.hide();mb.hide();},_ajaxResponse:function(xml){var el=xml?xml.documentElement:null;var inst=tinyMCE.selectedInstance,self=TinyMCE_SpellCheckerPlugin;var cmd=el?el.getAttribute("cmd"):null,err,id=el?el.getAttribute("id"):null;if(id)inst=tinyMCE.getInstanceById(id.substring(0,id.indexOf('|')));self._displayUI(inst);if(cmd=="suggest"&&id!=inst.editorId+"|"+self._counter)return;if(!el){inst.spellcheckerOn=false;tinyMCE.switchClass(inst.editorId+'_spellchecker','mceMenuButton');alert("Could not execute AJAX call, server didn't return valid a XML.");return;}err=el.getAttribute("error");if(err=="true"){inst.spellcheckerOn=false;tinyMCE.switchClass(inst.editorId+'_spellchecker','mceMenuButton');alert(el.getAttribute("msg"));return;}switch(cmd){case"spell":if(xml.documentElement.firstChild){self._markWords(inst.getDoc(),inst.getBody(),decodeURIComponent(el.firstChild.nodeValue).split('+'));inst.selection.moveToBookmark(inst.spellCheckerBookmark);if(tinyMCE.getParam('spellchecker_report_mispellings',false))alert(tinyMCE.getLang('lang_spellchecker_mpell_found','',true,{words:self._countWords(inst)}));}else alert(tinyMCE.getLang('lang_spellchecker_no_mpell','',true));self._checkDone(inst);break;case"suggest":self._buildMenu(el.firstChild?decodeURIComponent(el.firstChild.nodeValue).split('+'):null,10);self._contextMenu.show();break;}},_getWordSeparators:function(){var i,re='',ch=tinyMCE.getParam('spellchecker_word_separator_chars','\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}\u201d\u201c');for(i=0;i<ch.length;i++)re+='\\'+ch.charAt(i);return re;},_getWordList:function(n){var i,x,s,nv='',nl=tinyMCE.getNodeTree(n,new Array(),3),wl=new Array();var re=TinyMCE_SpellCheckerPlugin._getWordSeparators();for(i=0;i<nl.length;i++){if(!new RegExp('/SCRIPT|STYLE/').test(nl[i].parentNode.nodeName))nv+=nl[i].nodeValue+" ";}nv=nv.replace(new RegExp('([0-9]|['+re+'])','g'),' ');nv=tinyMCE.trim(nv.replace(/(\s+)/g,' '));nl=nv.split(/\s+/);for(i=0;i<nl.length;i++){s=false;for(x=0;x<wl.length;x++){if(wl[x]==nl[i]){s=true;break;}}if(!s&&nl[i].length>0)wl[wl.length]=nl[i];}return wl.join(' ');},_removeWords:function(doc,word){var i,c,nl=doc.getElementsByTagName("span");var self=TinyMCE_SpellCheckerPlugin;var inst=tinyMCE.selectedInstance,b=inst?inst.selection.getBookmark():null;word=typeof(word)=='undefined'?null:word;for(i=nl.length-1;i>=0;i--){c=tinyMCE.getAttrib(nl[i],'class');if((c=='mceItemHiddenSpellWord'||c=='mceItemHidden')&&(word==null||nl[i].innerHTML==word))self._removeWord(nl[i]);}if(b)inst.selection.moveToBookmark(b);},_checkDone:function(inst){var self=TinyMCE_SpellCheckerPlugin;var w=self._countWords(inst);if(w==0){self._removeWords(inst.getDoc());inst.spellcheckerOn=false;tinyMCE.switchClass(inst.editorId+'_spellchecker','mceMenuButton');}},_countWords:function(inst){var i,w=0,nl=inst.getDoc().getElementsByTagName("span"),c;var self=TinyMCE_SpellCheckerPlugin;for(i=nl.length-1;i>=0;i--){c=tinyMCE.getAttrib(nl[i],'class');if(c=='mceItemHiddenSpellWord')w++;}return w;},_removeWord:function(e){if(e!=null)tinyMCE.setOuterHTML(e,e.innerHTML);},_markWords:function(doc,n,wl){var i,nv,nn,nl=tinyMCE.getNodeTree(n,new Array(),3);var r1,r2,r3,r4,r5,w='';var re=TinyMCE_SpellCheckerPlugin._getWordSeparators();for(i=0;i<wl.length;i++){if(wl[i].length>0)w+=wl[i]+((i==wl.length-1)?'':'|');}for(i=0;i<nl.length;i++){nv=nl[i].nodeValue;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');if(r1.test(nv)||r2.test(nv)||r3.test(nv)||r4.test(nv)){nv=tinyMCE.xmlEncode(nv);nv=nv.replace(r5,'<span class="mceItemHiddenSpellWord">$1</span>$2');nv=nv.replace(r3,'<span class="mceItemHiddenSpellWord">$1</span>$2');nn=doc.createElement('span');nn.className="mceItemHidden";nn.innerHTML=nv;nl[i].parentNode.replaceChild(nn,nl[i]);}}},_buildMenu:function(sg,max){var i,self=TinyMCE_SpellCheckerPlugin,cm=self._contextMenu;cm.clear();if(sg!=null){cm.addTitle(tinyMCE.getLang('lang_spellchecker_sug','',true));for(i=0;i<sg.length&&i<max;i++)cm.addItem(sg[i],'tinyMCE.execCommand("mceSpellCheckReplace",false,"'+sg[i]+'");');cm.addSeparator();}else cm.addTitle(tinyMCE.getLang('lang_spellchecker_no_sug','',true));cm.addItem(tinyMCE.getLang('lang_spellchecker_ignore_word','',true),'tinyMCE.execCommand(\'mceSpellCheckIgnore\');');cm.addItem(tinyMCE.getLang('lang_spellchecker_ignore_words','',true),'tinyMCE.execCommand(\'mceSpellCheckIgnoreAll\');');cm.update();},_getAjaxHTTP:function(){try{return new ActiveXObject('Msxml2.XMLHTTP')}catch(e){try{return new ActiveXObject('Microsoft.XMLHTTP')}catch(e){return new XMLHttpRequest();}}},_sendAjax:function(u,f,m,a){var x=TinyMCE_SpellCheckerPlugin._getAjaxHTTP();x.open(m,u,true);x.onreadystatechange=function(){if(x.readyState==4)f(x.responseXML);};if(m=='post')x.setRequestHeader('Content-type','application/x-www-form-urlencoded');x.send(a);}};tinyMCE.addPlugin('spellchecker',TinyMCE_SpellCheckerPlugin);
program/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js
New file
@@ -0,0 +1,588 @@
/**
 * $Id: editor_plugin_src.js 28 2006-08-01 16:02:56Z spocke $
 *
 * @author Moxiecode
 * @copyright Copyright © 2004-2006, Moxiecode Systems AB, All rights reserved.
 */
tinyMCE.importPluginLanguagePack('spellchecker', 'en,fr,sv,nn,nb');
// Plucin static class
var TinyMCE_SpellCheckerPlugin = {
    _contextMenu : new TinyMCE_Menu(),
    _menu : new TinyMCE_Menu(),
    _counter : 0,
    _ajaxPage : '/tinyspell.php',
    getInfo : function() {
        return {
            longname : 'Spellchecker',
            author : 'Moxiecode Systems AB',
            authorurl : 'http://tinymce.moxiecode.com',
            infourl : 'http://tinymce.moxiecode.com/tinymce/docs/plugin_spellchecker.html',
            version : tinyMCE.majorVersion + "." + tinyMCE.minorVersion
        };
    },
    handleEvent : function(e) {
        var elm = tinyMCE.isMSIE ? e.srcElement : e.target;
        var inst = tinyMCE.selectedInstance, args = '';
        var self = TinyMCE_SpellCheckerPlugin;
        var cm = self._contextMenu;
        var p, p2, x, y, sx, sy, h, elm;
        // Handle click on word
        if ((e.type == "click" || e.type == "contextmenu") && elm) {
            do {
                if (tinyMCE.getAttrib(elm, 'class') == "mceItemHiddenSpellWord") {
                    inst.spellCheckerElm = elm;
                    // Setup arguments
                    args += 'id=' + inst.editorId + "|" + (++self._counter);
                    args += '&cmd=suggest&check=' + encodeURIComponent(elm.innerHTML);
                    args += '&lang=' + escape(inst.spellCheckerLang);
                    elm = inst.spellCheckerElm;
                    p = tinyMCE.getAbsPosition(inst.iframeElement);
                    p2 = tinyMCE.getAbsPosition(elm);
                    h = parseInt(elm.offsetHeight);
                    sx = inst.getBody().scrollLeft;
                    sy = inst.getBody().scrollTop;
                    x = p.absLeft + p2.absLeft - sx;
                    y = p.absTop + p2.absTop - sy + h;
                    cm.clear();
                    cm.addTitle(tinyMCE.getLang('lang_spellchecker_wait', '', true));
                    cm.show();
                    cm.moveTo(x, y);
                    inst.selection.selectNode(elm, false, false);
                    self._sendAjax(self.baseURL + self._ajaxPage, self._ajaxResponse, 'post', args);
                    tinyMCE.cancelEvent(e);
                    return false;
                }
            } while ((elm = elm.parentNode));
        }
        return true;
    },
    initInstance : function(inst) {
        var self = TinyMCE_SpellCheckerPlugin, m = self._menu, cm = self._contextMenu, e;
        tinyMCE.importCSS(inst.getDoc(), tinyMCE.baseURL + "/plugins/spellchecker/css/content.css");
        if (!tinyMCE.hasMenu('spellcheckercontextmenu')) {
            tinyMCE.importCSS(document, tinyMCE.baseURL + "/plugins/spellchecker/css/spellchecker.css");
            cm.init({drop_menu : false});
            tinyMCE.addMenu('spellcheckercontextmenu', cm);
        }
        if (!tinyMCE.hasMenu('spellcheckermenu')) {
            m.init({});
            tinyMCE.addMenu('spellcheckermenu', m);
        }
        inst.spellCheckerLang = 'en';
        self._buildSettingsMenu(inst, null);
        e = self._getBlockBoxLayer(inst).create('div', 'mceBlockBox', document.getElementById(inst.editorId + '_parent'));
        self._getMsgBoxLayer(inst).create('div', 'mceMsgBox', document.getElementById(inst.editorId + '_parent'));
    },
    _getMsgBoxLayer : function(inst) {
        if (!inst.spellCheckerMsgBoxL)
            inst.spellCheckerMsgBoxL = new TinyMCE_Layer(inst.editorId + '_spellcheckerMsgBox', false);
        return inst.spellCheckerMsgBoxL;
    },
    _getBlockBoxLayer : function(inst) {
        if (!inst.spellCheckerBoxL)
            inst.spellCheckerBoxL = new TinyMCE_Layer(inst.editorId + '_spellcheckerBlockBox', false);
        return inst.spellCheckerBoxL;
    },
    _buildSettingsMenu : function(inst, lang) {
        var i, ar = tinyMCE.getParam('spellchecker_languages', '+English=en').split(','), p;
        var self = TinyMCE_SpellCheckerPlugin, m = self._menu, c;
        m.clear();
        m.addTitle(tinyMCE.getLang('lang_spellchecker_langs', '', true));
        for (i=0; i<ar.length; i++) {
            if (ar[i] != '') {
                p = ar[i].split('=');
                c = 'mceMenuCheckItem';
                if (p[0].charAt(0) == '+') {
                    p[0] = p[0].substring(1);
                    if (lang == null) {
                        c = 'mceMenuSelectedItem';
                        inst.spellCheckerLang = p[1];
                    }
                }
                if (lang == p[1])
                    c = 'mceMenuSelectedItem';
                m.add({text : p[0], js : "tinyMCE.execInstanceCommand('" + inst.editorId + "','mceSpellCheckerSetLang',false,'" + p[1] + "');", class_name : c});
            }
        }
    },
    setupContent : function(editor_id, body, doc) {
        TinyMCE_SpellCheckerPlugin._removeWords(doc);
    },
    getControlHTML : function(cn) {
        switch (cn) {
            case "spellchecker":
                return TinyMCE_SpellCheckerPlugin._getMenuButtonHTML(cn, 'lang_spellchecker_desc', '{$pluginurl}/images/spellchecker.gif', 'lang_spellchecker_desc', 'mceSpellCheckerMenu', 'mceSpellCheck');
        }
        return "";
    },
    /**
     * Returns the HTML code for a normal button control.
     *
     * @param {string} id Button control id, this will be the suffix for the element id, the prefix is the editor id.
     * @param {string} lang Language variable key name to insert as the title/alt of the button image.
     * @param {string} img Image URL to insert, {$themeurl} and {$pluginurl} will be replaced.
     * @param {string} mlang Language variable key name to insert as the title/alt of the menu button image.
     * @param {string} mid Menu by id to display when the menu button is pressed.
     * @param {string} cmd Command to execute when the user clicks the button.
     * @param {string} ui Optional user interface boolean for command.
     * @param {string} val Optional value for command.
     * @return HTML code for a normal button based in input information.
     * @type string
     */
    _getMenuButtonHTML : function(id, lang, img, mlang, mid, cmd, ui, val) {
        var h = '', m, x;
        cmd = 'tinyMCE.hideMenus();tinyMCE.execInstanceCommand(\'{$editor_id}\',\'' + cmd + '\'';
        if (typeof(ui) != "undefined" && ui != null)
            cmd += ',' + ui;
        if (typeof(val) != "undefined" && val != null)
            cmd += ",'" + val + "'";
        cmd += ');';
        // Use tilemaps when enabled and found and never in MSIE since it loads the tile each time from cache if cahce is disabled
        if (tinyMCE.getParam('button_tile_map') && (!tinyMCE.isMSIE || tinyMCE.isOpera) && (m = tinyMCE.buttonMap[id]) != null && (tinyMCE.getParam("language") == "en" || img.indexOf('$lang') == -1)) {
            // Tiled button
            x = 0 - (m * 20) == 0 ? '0' : 0 - (m * 20);
            h += '<a id="{$editor_id}_' + id + '" href="javascript:' + cmd + '" onclick="' + cmd + 'return false;" onmousedown="return false;" class="mceTiledButton mceButtonNormal" target="_self">';
            h += '<img src="{$themeurl}/images/spacer.gif" style="background-position: ' + x + 'px 0" title="{$' + lang + '}" />';
            h += '<img src="{$themeurl}/images/button_menu.gif" title="{$' + lang + '}" class="mceMenuButton" onclick="' + mcmd + 'return false;" />';
            h += '</a>';
        } else {
            if (tinyMCE.isMSIE && !tinyMCE.isOpera)
                h += '<span id="{$editor_id}_' + id + '" class="mceMenuButton" onmouseover="tinyMCE.plugins.spellchecker._menuButtonEvent(\'over\',this);" onmouseout="tinyMCE.plugins.spellchecker._menuButtonEvent(\'out\',this);">';
            else
                h += '<span id="{$editor_id}_' + id + '" class="mceMenuButton">';
            h += '<a href="javascript:' + cmd + '" onclick="' + cmd + 'return false;" onmousedown="return false;" class="mceMenuButtonNormal" target="_self">';
            h += '<img src="' + img + '" title="{$' + lang + '}" /></a>';
            h += '<a href="#" onclick="tinyMCE.plugins.spellchecker._toggleMenu(\'{$editor_id}\',\'' + mid + '\');return false;" onmousedown="return false;"><img src="{$themeurl}/images/button_menu.gif" title="{$' + lang + '}" class="mceMenuButton" />';
            h += '</a></span>';
        }
        return h;
    },
    _menuButtonEvent : function(e, o) {
        if (o.className == 'mceMenuButtonFocus')
            return;
        if (e == 'over')
            o.className = o.className + ' mceMenuHover';
        else
            o.className = o.className.replace(/\s.*$/, '');
    },
    _toggleMenu : function(editor_id, id) {
        var self = TinyMCE_SpellCheckerPlugin;
        var e = document.getElementById(editor_id + '_spellchecker');
        var inst = tinyMCE.getInstanceById(editor_id);
        if (self._menu.isVisible()) {
            tinyMCE.hideMenus();
            return;
        }
        tinyMCE.lastMenuBtnClass = e.className.replace(/\s.*$/, '');
        tinyMCE.switchClass(editor_id + '_spellchecker', 'mceMenuButtonFocus');
        self._menu.moveRelativeTo(e, 'bl');
        self._menu.moveBy(tinyMCE.isMSIE && !tinyMCE.isOpera ? 0 : 1, -1);
        if (tinyMCE.isOpera)
            self._menu.moveBy(0, -2);
        self._onMenuEvent(inst, self._menu, 'show');
        self._menu.show();
        tinyMCE.lastSelectedMenuBtn = editor_id + '_spellchecker';
    },
    _onMenuEvent : function(inst, m, n) {
        TinyMCE_SpellCheckerPlugin._buildSettingsMenu(inst, inst.spellCheckerLang);
    },
    execCommand : function(editor_id, element, command, user_interface, value) {
        var inst = tinyMCE.getInstanceById(editor_id), self = TinyMCE_SpellCheckerPlugin, args = '', co, bb, mb, nl, i, e;
        // Handle commands
        switch (command) {
            case "mceSpellCheck":
                if (!inst.spellcheckerOn) {
                    inst.spellCheckerBookmark = inst.selection.getBookmark();
                    // Setup arguments
                    args += 'id=' + inst.editorId + "|" + (++self._counter);
                    args += '&cmd=spell&check=' + encodeURIComponent(self._getWordList(inst.getBody())).replace( /\'/g, '%27' );
                    args += '&lang=' + escape(inst.spellCheckerLang);
                    co = document.getElementById(inst.editorId + '_parent').firstChild;
                    bb = self._getBlockBoxLayer(inst);
                    bb.moveRelativeTo(co, 'tl');
                    bb.resizeTo(co.offsetWidth, co.offsetHeight);
                    bb.show();
                    // Setup message box
                    mb = self._getMsgBoxLayer(inst);
                    e = mb.getElement();
                    e.innerHTML = '<span>' + tinyMCE.getLang('lang_spellchecker_swait', '', true) + '</span>';
                    mb.show();
                    mb.moveRelativeTo(co, 'cc');
                    if (tinyMCE.isMSIE && !tinyMCE.isOpera) {
                        nl = co.getElementsByTagName('select');
                        for (i=0; i<nl.length; i++)
                            nl[i].disabled = true;
                    }
                    inst.spellcheckerOn = true;
                    tinyMCE.switchClass(editor_id + '_spellchecker', 'mceMenuButtonSelected');
                    self._sendAjax(self.baseURL + self._ajaxPage, self._ajaxResponse, 'post', args);
                } else {
                    self._removeWords(inst.getDoc());
                    inst.spellcheckerOn = false;
                    tinyMCE.switchClass(editor_id + '_spellchecker', 'mceMenuButton');
                }
                return true;
            case "mceSpellCheckReplace":
                if (inst.spellCheckerElm)
                    tinyMCE.setOuterHTML(inst.spellCheckerElm, value);
                self._checkDone(inst);
                self._contextMenu.hide();
                self._menu.hide();
                return true;
            case "mceSpellCheckIgnore":
                if (inst.spellCheckerElm)
                    self._removeWord(inst.spellCheckerElm);
                self._checkDone(inst);
                self._contextMenu.hide();
                self._menu.hide();
                return true;
            case "mceSpellCheckIgnoreAll":
                if (inst.spellCheckerElm)
                    self._removeWords(inst.getDoc(), inst.spellCheckerElm.innerHTML);
                self._checkDone(inst);
                self._contextMenu.hide();
                self._menu.hide();
                return true;
            case "mceSpellCheckerSetLang":
                tinyMCE.hideMenus();
                inst.spellCheckerLang = value;
                self._removeWords(inst.getDoc());
                inst.spellcheckerOn = false;
                tinyMCE.switchClass(editor_id + '_spellchecker', 'mceMenuButton');
                return true;
        }
        // Pass to next handler in chain
        return false;
    },
    cleanup : function(type, content, inst) {
        switch (type) {
            case "get_from_editor_dom":
                TinyMCE_SpellCheckerPlugin._removeWords(content);
                inst.spellcheckerOn = false;
                break;
        }
        return content;
    },
    // Private plugin specific methods
    _displayUI : function(inst) {
        var self = TinyMCE_SpellCheckerPlugin;
        var bb = self._getBlockBoxLayer(inst);
        var mb = self._getMsgBoxLayer(inst);
        var nl, i;
        var co = document.getElementById(inst.editorId + '_parent').firstChild;
        if (tinyMCE.isMSIE && !tinyMCE.isOpera) {
            nl = co.getElementsByTagName('select');
            for (i=0; i<nl.length; i++)
                nl[i].disabled = false;
        }
        bb.hide();
        mb.hide();
    },
    _ajaxResponse : function(xml) {
        var el = xml ? xml.documentElement : null;
        var inst = tinyMCE.selectedInstance, self = TinyMCE_SpellCheckerPlugin;
        var cmd = el ? el.getAttribute("cmd") : null, err, id = el ? el.getAttribute("id") : null;
        if (id)
            inst = tinyMCE.getInstanceById(id.substring(0, id.indexOf('|')));
        self._displayUI(inst);
        // Ignore suggestions for other ajax responses
        if (cmd == "suggest" && id != inst.editorId + "|" + self._counter)
            return;
        if (!el) {
            inst.spellcheckerOn = false;
            tinyMCE.switchClass(inst.editorId + '_spellchecker', 'mceMenuButton');
            alert("Could not execute AJAX call, server didn't return valid a XML.");
            return;
        }
        err = el.getAttribute("error");
        if (err == "true") {
            inst.spellcheckerOn = false;
            tinyMCE.switchClass(inst.editorId + '_spellchecker', 'mceMenuButton');
            alert(el.getAttribute("msg"));
            return;
        }
        switch (cmd) {
            case "spell":
                if (xml.documentElement.firstChild) {
                    self._markWords(inst.getDoc(), inst.getBody(), decodeURIComponent(el.firstChild.nodeValue).split('+'));
                    inst.selection.moveToBookmark(inst.spellCheckerBookmark);
                    if(tinyMCE.getParam('spellchecker_report_mispellings', false))
                        alert(tinyMCE.getLang('lang_spellchecker_mpell_found', '', true, {words : self._countWords(inst)}));
                } else
                    alert(tinyMCE.getLang('lang_spellchecker_no_mpell', '', true));
                self._checkDone(inst);
                break;
            case "suggest":
                self._buildMenu(el.firstChild ? decodeURIComponent(el.firstChild.nodeValue).split('+') : null, 10);
                self._contextMenu.show();
                break;
        }
    },
    _getWordSeparators : function() {
        var i, re = '', ch = tinyMCE.getParam('spellchecker_word_separator_chars', '\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}§©«®±¶·¸»¼½¾¿×÷¤\u201d\u201c');
        for (i=0; i<ch.length; i++)
            re += '\\' + ch.charAt(i);
        return re;
    },
    _getWordList : function(n) {
        var i, x, s, nv = '', nl = tinyMCE.getNodeTree(n, new Array(), 3), wl = new Array();
        var re = TinyMCE_SpellCheckerPlugin._getWordSeparators();
        for (i=0; i<nl.length; i++) {
            if (!new RegExp('/SCRIPT|STYLE/').test(nl[i].parentNode.nodeName))
                nv += nl[i].nodeValue + " ";
        }
        nv = nv.replace(new RegExp('([0-9]|[' + re + '])', 'g'), ' ');
        nv = tinyMCE.trim(nv.replace(/(\s+)/g, ' '));
        nl = nv.split(/\s+/);
        for (i=0; i<nl.length; i++) {
            s = false;
            for (x=0; x<wl.length; x++) {
                if (wl[x] == nl[i]) {
                    s = true;
                    break;
                }
            }
            if (!s && nl[i].length > 0)
                wl[wl.length] = nl[i];
        }
        return wl.join(' ');
    },
    _removeWords : function(doc, word) {
        var i, c, nl = doc.getElementsByTagName("span");
        var self = TinyMCE_SpellCheckerPlugin;
        var inst = tinyMCE.selectedInstance, b = inst ? inst.selection.getBookmark() : null;
        word = typeof(word) == 'undefined' ? null : word;
        for (i=nl.length-1; i>=0; i--) {
            c = tinyMCE.getAttrib(nl[i], 'class');
            if ((c == 'mceItemHiddenSpellWord' || c == 'mceItemHidden') && (word == null || nl[i].innerHTML == word))
                self._removeWord(nl[i]);
        }
        if (b)
            inst.selection.moveToBookmark(b);
    },
    _checkDone : function(inst) {
        var self = TinyMCE_SpellCheckerPlugin;
        var w = self._countWords(inst);
        if (w == 0) {
            self._removeWords(inst.getDoc());
            inst.spellcheckerOn = false;
            tinyMCE.switchClass(inst.editorId + '_spellchecker', 'mceMenuButton');
        }
    },
    _countWords : function(inst) {
        var i, w = 0, nl = inst.getDoc().getElementsByTagName("span"), c;
        var self = TinyMCE_SpellCheckerPlugin;
        for (i=nl.length-1; i>=0; i--) {
            c = tinyMCE.getAttrib(nl[i], 'class');
            if (c == 'mceItemHiddenSpellWord')
                w++;
        }
        return w;
    },
    _removeWord : function(e) {
        if (e != null)
            tinyMCE.setOuterHTML(e, e.innerHTML);
    },
    _markWords : function(doc, n, wl) {
        var i, nv, nn, nl = tinyMCE.getNodeTree(n, new Array(), 3);
        var r1, r2, r3, r4, r5, w = '';
        var re = TinyMCE_SpellCheckerPlugin._getWordSeparators();
        for (i=0; i<wl.length; i++) {
            if (wl[i].length > 0)
                w += wl[i] + ((i == wl.length-1) ? '' : '|');
        }
        for (i=0; i<nl.length; i++) {
            nv = nl[i].nodeValue;
            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');
            if (r1.test(nv) || r2.test(nv) || r3.test(nv) || r4.test(nv)) {
                nv = tinyMCE.xmlEncode(nv);
                nv = nv.replace(r5, '<span class="mceItemHiddenSpellWord">$1</span>$2');
                nv = nv.replace(r3, '<span class="mceItemHiddenSpellWord">$1</span>$2');
                nn = doc.createElement('span');
                nn.className = "mceItemHidden";
                nn.innerHTML = nv;
                // Remove old text node
                nl[i].parentNode.replaceChild(nn, nl[i]);
            }
        }
    },
    _buildMenu : function(sg, max) {
        var i, self = TinyMCE_SpellCheckerPlugin, cm = self._contextMenu;
        cm.clear();
        if (sg != null) {
            cm.addTitle(tinyMCE.getLang('lang_spellchecker_sug', '', true));
            for (i=0; i<sg.length && i<max; i++)
                cm.addItem(sg[i], 'tinyMCE.execCommand("mceSpellCheckReplace",false,"' + sg[i] + '");');
            cm.addSeparator();
        } else
            cm.addTitle(tinyMCE.getLang('lang_spellchecker_no_sug', '', true));
        cm.addItem(tinyMCE.getLang('lang_spellchecker_ignore_word', '', true), 'tinyMCE.execCommand(\'mceSpellCheckIgnore\');');
        cm.addItem(tinyMCE.getLang('lang_spellchecker_ignore_words', '', true), 'tinyMCE.execCommand(\'mceSpellCheckIgnoreAll\');');
        cm.update();
    },
    _getAjaxHTTP : function() {
        try {
            return new ActiveXObject('Msxml2.XMLHTTP')
        } catch (e) {
            try {
                return new ActiveXObject('Microsoft.XMLHTTP')
            } catch (e) {
                return new XMLHttpRequest();
            }
        }
    },
    /**
     * Perform AJAX call.
     *
     * @param {string} u URL of AJAX service.
     * @param {function} f Function to call when response arrives.
     * @param {string} m Request method post or get.
     * @param {Array} a Array with arguments to send.
     */
    _sendAjax : function(u, f, m, a) {
        var x = TinyMCE_SpellCheckerPlugin._getAjaxHTTP();
        x.open(m, u, true);
        x.onreadystatechange = function() {
            if (x.readyState == 4)
                f(x.responseXML);
        };
        if (m == 'post')
            x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        x.send(a);
    }
};
// Register plugin
tinyMCE.addPlugin('spellchecker', TinyMCE_SpellCheckerPlugin);
program/js/tiny_mce/plugins/spellchecker/images/spellchecker.gif
program/js/tiny_mce/plugins/spellchecker/images/wline.gif
program/js/tiny_mce/plugins/spellchecker/langs/en.js
New file
@@ -0,0 +1,15 @@
// UK lang variables
tinyMCE.addToLang('spellchecker',{
    desc : 'Toggle spellchecker',
    menu : 'Spellchecker settings',
    ignore_word : 'Ignore word',
    ignore_words : 'Ignore all',
    langs : 'Languages',
    wait : 'Please wait...',
    swait : 'Spellchecking, please wait...',
    sug : 'Suggestions',
    no_sug : 'No suggestions',
    no_mpell : 'No misspellings found.',
    mpell_found : 'Found {$words} misspellings.'
});
program/js/tiny_mce/plugins/spellchecker/tinyspell.php
New file
@@ -0,0 +1,142 @@
<?php
/**
 * $RCSfile: tinyspell.php,v $
 * $Revision: 1.1 $
 * $Date: 2006/03/14 17:33:47 $
 *
 * @author Moxiecode
 * @copyright Copyright © 2004-2006, Moxiecode Systems AB, All rights reserved.
 */
    // Ignore the Notice errors for now.
    error_reporting(E_ALL ^ E_NOTICE);
    require_once("config.php");
    $id = sanitize($_POST['id'], "loose");
    if (!$spellCheckerConfig['enabled']) {
        header('Content-type: text/xml; charset=utf-8');
        echo '<?xml version="1.0" encoding="utf-8" ?><res id="' . $id . '" error="true" msg="You must enable the spellchecker by modifying the config.php file." />';
        die;
    }
    // Basic config
    $defaultLanguage = $spellCheckerConfig['default.language'];
    $defaultMode = $spellCheckerConfig['default.mode'];
    // Normaly not required to configure
    $defaultSpelling = $spellCheckerConfig['default.spelling'];
    $defaultJargon = $spellCheckerConfig['default.jargon'];
    $defaultEncoding = $spellCheckerConfig['default.encoding'];
    $outputType = "xml"; // Do not change
    // Get input parameters.
    $check = urldecode($_REQUEST['check']);
    $cmd = sanitize($_REQUEST['cmd']);
    $lang = sanitize($_REQUEST['lang'], "strict");
    $mode = sanitize($_REQUEST['mode'], "strict");
    $spelling = sanitize($_REQUEST['spelling'], "strict");
    $jargon = sanitize($_REQUEST['jargon'], "strict");
    $encoding = sanitize($_REQUEST['encoding'], "strict");
    $sg = sanitize($_REQUEST['sg'], "bool");
    $words = array();
    $validRequest = true;
    if (empty($check))
        $validRequest = false;
    if (empty($lang))
        $lang = $defaultLanguage;
    if (empty($mode))
        $mode = $defaultMode;
    if (empty($spelling))
        $spelling = $defaultSpelling;
    if (empty($jargon))
        $jargon = $defaultJargon;
    if (empty($encoding))
        $encoding = $defaultEncoding;
    function sanitize($str, $type="strict") {
        switch ($type) {
            case "strict":
                $str = preg_replace("/[^a-zA-Z0-9_\-]/i", "", $str);
            break;
            case "loose":
                $str = preg_replace("/</i", "&gt;", $str);
                $str = preg_replace("/>/i", "&lt;", $str);
            break;
            case "bool":
                if ($str == "true" || $str == true)
                    $str = true;
                else
                    $str = false;
            break;
        }
        return $str;
    }
    $result = array();
    $tinyspell = new $spellCheckerConfig['class']($spellCheckerConfig, $lang, $mode, $spelling, $jargon, $encoding);
    if (count($tinyspell->errorMsg) == 0) {
        switch($cmd) {
            case "spell":
                // Space for non-exec version and \n for the exec version.
                $words = preg_split("/ |\n/", $check, -1, PREG_SPLIT_NO_EMPTY);
                $result = $tinyspell->checkWords($words);
            break;
            case "suggest":
                $result = $tinyspell->getSuggestion($check);
            break;
            default:
                // Just use this for now.
                $tinyspell->errorMsg[] = "No command.";
                $outputType = $outputType . "error";
            break;
        }
    } else
        $outputType = $outputType . "error";
    if (!$result)
        $result = array();
    // Output data
    switch($outputType) {
        case "xml":
            header('Content-type: text/xml; charset=utf-8');
            $body  = '<?xml version="1.0" encoding="utf-8" ?>';
            $body .= "\n";
            if (count($result) == 0)
                $body .= '<res id="' . $id . '" cmd="'. $cmd .'" />';
            else
                $body .= '<res id="' . $id . '" cmd="'. $cmd .'">'. urlencode(implode(" ", $result)) .'</res>';
            echo $body;
        break;
        case "xmlerror";
            header('Content-type: text/xml; charset=utf-8');
            $body  = '<?xml version="1.0" encoding="utf-8" ?>';
            $body .= "\n";
            $body .= '<res id="' . $id . '" cmd="'. $cmd .'" error="true" msg="'. implode(" ", $tinyspell->errorMsg) .'" />';
            echo $body;
        break;
        case "html":
            var_dump($result);
        break;
        case "htmlerror":
            echo "Error";
        break;
    }
?>