imgupload is a tinyMCE plugin which adds file upload to the list of choices available to a tinyMCE editor instance. It was written in compliance with tinyMCE 3.3.5.1 – as I understand them, using www.tinymce.com documentation, reverse engineering, and trial and error.
imgupload will work both with jQuery and without. It automatically detects the presence of jQuery and will work with either the tinyMCE jQuery plugin or using plain, vanilla tinyMCE.
This plugin was developed in the ‘approved manner’: by copying the plugin called example and hacking it up. Much of the code follows example very closely except that is handles installations with and w/o jQuery and does a lot of event handling and form rewriting to create a ‘better user experience’. Aside from that and a bunch of CSS, they are almost identical.
Licensed under the Lesser GNU Public License, version 3. See LGPL-3.txt for details.
WARNING: USING THIS CODE IS INHERENTLY DANGEROUS BECAUSE IT ALLOWS USERS TO UPLOAD FILES TO YOUR SERVER. IT HAS NO MEANINGFUL SECURITY. USE AT YOUR OWN RISK.
THIS SOFTWARE IS MADE AVAILABLE WITHOUT WARRANTY. USE AT YOUR OWN RISK.
The following warranty disclaimer and limitation of liability are ‘borrowed’ from the copying provisions from GNU Emacs and EXPLICITLY apply to this code:
Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
Menu:
While it is possible to modify this code to make it relatively secure in specific instances, this is not possible for a general distribution. This is because authentication, authorization, and file de-lousing are beyond the scope of this project.
Here are some suggestions which could be implemented in order to reduce the risk of allowing users to upload files. Risk cannot be completely eliminated.
As always, either consult a security expert or become one yourself before trying any of these.
In other words: USE AT YOUR OWN RISK. and I’M NOT RESPONSIBLE OR LIABLE FOR ANYTHING THAT HAPPENS.
The plugin follows the required tinyMCE plugin structure:
imgupload/ - (root directory for plugin - the name is signficant)
/dialog.htm - the html for the popup form
/editor_plugin.js - the javascript which initializes the plugin - compressed
/editor_plugin_src.js - development version of initialization plugin - source of editor_plugin.js
/upload-file.php - PHP script which handles server side of file upload
/Spec.textile - this document in Textile format
/css/ – (directory for plugin css)
/css/imgupload.css – css for plugin
/img/ – (directory for images used by plugin)
/img/buttons.png – local copy of the button background sprite
/img/icon.gif – button icon which activates the plugin – derived from the image icon source
/img/icon.psd – photoshop source for icon.gif
/js/ – (javascript directory)
/js/dialog.js – javascript which initializes popup and handles popup events
/langs/ – (language pack directory – add translations here)
/langs/en.js – plugin initialization language phrases – US English
/langs/en_dlg.js _ popup language phrases – US English
/Slides/ – directory for presentation slides
/Slides/tinyMCE-jQuery-plugin.ai – Adobe Illustrator version of slides explaining the plugin design
This plugin allows a user to upload a file to a server from within a tinyMCE editor.
To clarify, tinyMCE is not a single editor, but it is a Javascript application which can create as many editors as desired for a given web page – where each separate editor provides rich text editing of a specific page element – typically a <textarea> element.
All the editors are created when tinyMCE is initialized upon page load.
It is not clear when the plugin is initialized, but it likely happens upon editor creation – for each editor. See the tinyMCE website for detailed information.
Each plugin instance manages a popup window. The popup windows are initialized when the user generates an event by clicking the button bound to the plugin and execute in their own window context. This necessitates passing some parameters to the popup initialization routine from the plugin initialization logic.
Once initialized, this popup is completely event driven.
Configuration is takes place in three places:
Here is a sample snip from the tinyMCE configuration object:
. . .plugins: ’...,imgupload’, . . . theme_advanced_buttons2: ’...,anchor,image,imgupload,...’; . . .
Locate the lines contaning ‘upload-file.php’ and change them to the URL for your specific server side code. There are two places and both are commented
See server configuration below.
The plugin is initialized by running ‘editor_plugin.js’ – or ‘editor_plugin_src.js’, which is the development version of ‘editor_plugin.js’.
editor_plugin_src.js is responsible for:
Loading a language pack seems to do two things:
If you look at /langs/en.js and /langs/en_dlg.js, you will see that those files actually create objects which are fields of the en object and the names of the language packs are the field names they define.
The result of creating the plugin consists of an identifier and a Javascript object.
The identifier is the string ‘tinymce.plugins.ImgUploadPlugin’
The Javascript object contains three functions:
tinymce.plugins.ImgUploadPlugin.init() creates a variable called jQuery which it passes to the plugin. This is done by checking to see if window.jQuery is defined or not. If not, the jQuery is set to null, otherwise to window.jQuery. This value is used in creating the closure passed to ed.addCommand(); this function is called every time the plugin popup window is openned.
This is necessary because the popup window runs in a context which does not contain the jQuery object.
Once everything is set up, all the action happens in the Popup.
The popup consists of three main pieces and some ancillary stuff:
dialog.htm is a straight forward form with a couple of buttons. The only interesting departure is that the output of the form targets an iframe contained in dialog.htm rather than the user visible output.
This is the AJAX-ish hack which allows us to display an animation during the file upload and to process the upload results in the client rather than on the server. This allows the plugin to be retargeted to other languages.
dialog.js does three things:
The popup dialog object ImgUploadDialog contains these attributes:
Each event handler is responsible for binding and unbinding events and modifying the popup window as is appropriate.
All of the methods have code which runs with and without jQuery. These two halves do the identical processing – within normal bugginess parameters.
In more detail:
Initializes the frm_elts dictionary. Hides all parts of the form except for upload file selection and Cancel button. Writes a nice message requesting the user to select a file.
Sets up upload_file_change() to run when the upload file is defined
Copies the upload file name to the destination file field and runs test_on_server()
If the selected file is legal, then the destination file name and the Upload / Replace button are made visible. A message is written either saying the file can be uploaded or a warning that uploading now will replace it.
Focus is transferred to the destination file field.
dest_fname_change() is set to respond to a change in destination file name
upload() is set to respond to clicking the Upload / Replace button or a submit of the form.
If the selected file is not legal, then dest_fname_change() and upload() are blocked from firing, a warning message is written, and focus goes to the upload_file field
Runs test_on_server().
This results in exactly the same final condition as detailed in upload_file_change() at the end of the previous paragraph.
Removes the upload_file and destination_file fields from the form.
Removes event handlers for the form and upload_file and destination file fields.
Writes an ‘Uploading…’ message.
Removes and disables the Upload/Replace and Cancel buttons
Sets upload_finished() to run when the iframe receives the output of file-upload.php
Submits the form to file-upload.php on the server.
{background:red}. FIXME: need to add a timeout to this thing so it will detect a hung server process
Unwires all event handlers.
Sets up user_finished() to run when the Close button is clicked (same button as is used to run upload(), but with different skin).
Copies contents of iframe to message area of popup
Closes the popup window.
This does the bulk of the work. It sends a Synchronous AJAX request to run upload-file.php and processes the result.
If the result is a success, then it sets up the form to either modify the destination file name, do the upload, or cancel out.
If the result is a failure, then it writes a message and disables upload and modification of the destination file name.
The server code supplied is the single file: upload-file.php.
It operates in two mode – specified by the imgupload_command POST variable:
In addition to imgupload_command, upload-file.php requires two more POST variables defined: imgupload_upload_file and imgupload_dest_fname.
There are two significant variables which determine where the file will be dropped and what files may be uploaded. These are $dest_dir and $legal_exts.
Here is a more detailed description:
Configuration requires hacking the upload utility. Read the comments in the supplied sample command: upload-file.php.
Three things need to be done:
First, the two parameters listed at the top of the script must be specicalized for your application.
Second, you SHOULD add some sort of security. The supplied command is insecure. Aside from the rudimentary test of the file name extension, anything can be loaded into the file reception directory. This is a very, very bad idea.
Finally, the server code needs to be someplace where the plugin can find it. As written, it expects to find the upload utility in the same directory the plugin lives in -
Variables which need to be set:
Uploads the file specified in $_FILES[‘upload_file’].
After doing various tests and resolving the upload file name, this returns HTML which is dumped into the form target iframe. This is directly human readable and needs no translation.
I decided to not use a JSON or XML response because (at least my current version of) Firefox (and other browsers) balk when receiving data of type application/json or application/xml and ask what to do. It seems simpler, faster, and generally better to pass the result directly to the user than to ask them what do do with the data and then reformat the result and ask them what to do.
After performing pretty much the same tests and file name resolution, this returns a JSON structure. Naturally this is designed to be an AJAX call and is used to control the user experience – warning when the file may be overwritten, etc – prior to doing the upload.
The returned JSON object has the following keys: