Tuesday, February 26, 2008

Getting TinyMCE 3.0 Spellchecker to work with Rails

In new version of TinyMCE editor there are many changes in plug ins. The Plug in code is changed. The popular spell checker plug in now uses JSON as transport mechanism instead of XML. In my example we are using Aspell to check spellings, so you must have Aspell installed on your system for this example to work

Issue this command on CLI to check if Aspell is installed:

% aspell

You should see help output related to command

First of all you need to install JSON gem to parse JSON requests

% sudo gem install json

Then write this action in a controller:

require json

def spellcheck
raw = request.env['RAW_POST_DATA']
req = JSON.parse(raw)
lang = req["params"][0]
if req["method"] == 'checkWords'
text_to_check = req["params"][1].join(" ")
text_to_check = req["params"][1]
suggestions = check_spelling_new(text_to_check, req["method"], lang)
render :json => {"id" => nil, "result" => suggestions, "error" => nil}.to_json

Now also write this method in private section of controller:

def check_spelling_new(spell_check_text, command, lang)
json_response_values = Array.new
spell_check_response = `echo "#{spell_check_text}" | aspell -a -l #{lang}`
if (spell_check_response != '')
spelling_errors = spell_check_response.split(' ').slice(1..-1)
if (command == 'checkWords')
i = 0
while i < spelling_errors.length
if (spelling_errors[i].to_s.index('&') == 0)
match_data = spelling_errors[i + 1]
json_response_values << match_data
i += 1
elsif (command == 'getSuggestions')
arr = spell_check_response.split(':')
suggestion_string = arr[1]
suggestions = suggestion_string.split(',')
for suggestion in suggestions
json_response_values << suggestion
return json_response_values

now you need to open /plugins/spellchecker/editor_plugin.js file under your Tiny MCE directory. Search for this line;

t.url = url;

and change it to:

t.url = 'http://www.yourhostname.com';

then search for this line:

var t = this, url = t.editor.getParam("spellchecker_rpc_url", this.url+'/rpc.php');

and change it to:

var t = this, url = t.editor.getParam("spellchecker_rpc_url", this.url+'/spellcheck');

and to make this work just add this route to routes.rb under config folder

map.connect '/spellcheck', :controller => 'your_controller_name', :action => 'spellcheck'
#replace your_controller_name with name of our controller in which the spellcheck action is written


Marcus Crafter said...

Great work, looking forward to integrating spell checking into our Rails app as well. Thanks mate!

Tron said...

Which version of Rails are you using? I tried your suggestions with Rails 2. However, Rails sees the incoming request as a GET so none of the post data is accessible to the controller.

Also, instead of modifying the js source, I used spellchecker_rpc_url when creating an instance of a tinyMCE editor. That seems to work.

Hasham Malik said...

I wrote this while using rails 1.2.3. Thanks, for tip about passing spellchecker_rpc_url when calling init. I ll use that my self.

Scott said...

Thanks Hasham. While I was searching for info on this subject, your post came up next to this one from Mike at Gusto, which others might find helpful as well.

I presume that Mike's code was written for TinyMCE 2.x, which uses XML, and you've updated his code for TinyMCE 3.x, which uses JSON?

Srini said...

Some issues:

1) There's a command line injection vulnerability in the given example code. The problem is this line:

echo "#{spell_check_text}" | aspell -a -l #{lang}

A malicious user could bypass tiny_mce and call the given controller action directly. All they'd have to do is be clever about using quotation marks and they can execute anything with the same priviliges as the app server. It's probably better to do something like this:

tf = Tempfile.new("aspell_buf")
tf.seek 0
spell_check_response = `cat #{tf.path} | aspell -a -l #{lang}`

2) To get this to work in rails 2.0.2, I had to add the following to environment.rb:

ActionController::Base.param_parsers[Mime::JSON] = Proc.new do |data|

AB said...

There is another way to do this,