Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Paint tool integration

From MoodleDocs
Revision as of 12:54, 31 July 2009 by Mihai Sucan (talk | contribs) (detailing my work on Moodle 1.9 integration (to be continued))

PaintWeb is the paint tool which is going to be integrated into Moodle. For more information about PaintWeb please read the project specification.

Overview

PaintWeb is currently a strictly client-side project, a Web application. It provides an API for developers who wish to extend its functionality, via new drawing tools, new extensions and new commands.

The plan is to integrate PaintWeb in Moodle in the appropriate places. Meaning, in some cases, users would like to be able to edit images, in place, in the HTML editor (TinyMCE mostly). Another place of integration would be the file manager - there users should be allowed to pick images files and edit them. Another use-case for PaintWeb is separate loading, stand-alone usage - for example a page which only allows the user to draw on the Canvas and save the result.

PaintWeb already includes a TinyMCE plugin which allows users to start editing any image in the HTML document. The plugin allows users to start editing an image by clicking an Edit button which shows overlayed on top of images when they are selected. Additionally, users can right-click an image to pick the Edit in PaintWeb. Lastly, when the advanced theme is used, the paintwebEdit button is also available.

Things to take into consideration:

  • PaintWeb can only edit images from the same domain, due to security restrictions. This means that any image from a different domain is either denied from editing, or we can use a server-side script which downloads the image locally on the server. The latter option is more cumbersome, and I believe it implies some security risks: the user can point to arbitrary malicious files which the server downloads and sends back to the browser. The TinyMCE plugin I have does not allow the users to edit of any image from a different domain.
  • The TinyMCE plugin needs to be loaded dynamically. The PaintWeb image editor should not show up in all cases where TinyMCE is used. Thus, Moodle needs an API which other developers can use to show the HTML editor and configure it: "hey, I want PaintWeb as well!" or not.
  • There's an important difference between the use of PaintWeb inside TinyMCE and standalone use. Inside TinyMCE the user already has a dialog for inserting images - thus in PaintWeb we do not need an option to load another image. In PaintWeb-TinyMCE we only need the options to save and cancel the changes. In a standalone instance of PaintWeb we might want to allow the user to load and save images at will. The file manager use-case is the same as in TinyMCE: the file manager handles the files itself, PaintWeb should be strictly the image editor (with save/cancel).

Given the above, here is what I consider needs to be done:

  • Moodle 2 has an API for inserting the HTML editor (TinyMCE) anywhere desired. This API needs to be extended such that the developer can tell which plugins need to be loaded - in this case PaintWeb.
  • Moodle 2 has a File API which needs to be used by PaintWeb when the file save/load operations are performed. On the client-side of things this can be done by writing an extension for PaintWeb which handles events like imageSave. PaintWeb in Moodle needs some "common backend" which handles image save/load in all use-cases.
  • It should be noted that having a PaintWeb extensions specific for Moodle would replace the need of having custom patches applied to PaintWeb - like Moodle does for TinyMCE.
  • The PaintWeb script needs to communicate with the server to perform file load/save. So, on the server-side these operations can be handled by a piece of common code, e.g. /lib/paintweb/ext/moodle/lib.php. Suggestions for a different file name / place are welcome. This PHP script needs to use the File API to perform the desired operations. An API needs to be exposed by this file, an API others can use to integrate PaintWeb in a standalone page. For example paintweb_setup() which sets-up the required JS file in the page, for the current page. Then paintweb_insert(options) to insert the PaintWeb initialization code, with user-defined options.
  • The upcoming file manager needs an API for adding "file handlers". For example, PaintWeb can be a file handler for images. Thus, a button, or some context menu item for images could be displayed Edit this image in PaintWeb.

An additional vector of PaintWeb integration is Moodleforms, something like $mform->addElement('paintweb', 'targetInputElement', options).

The Moodle 2.0 editors API

Currently, developers who want to have a textarea element in their page with TinyMCE can do the following:

// setup the page requirements (JS files needed to be loaded) editors_head_setup();

// get an instance of the editor $editor = get_preferred_texteditor(FORMAT_HTML);

// use the editor: textarea element ID, and options. $editor->use_editor('elementId', array());

echo '<textarea rows="20" cols="10" id="elementId">some HTML</textarea>';

According to Petr Škoda, the developer of the API, the API still needs lots of work. Personally I consider the API only needs ... further work. Here's how I'd like to do things:

$editor->use_editor('elementId',

 array('imageEditing' => true,
       'mathEditing' => true
 )

);

Basically, I don't think there's something inherently "bad" about the current API. I only want to tell the editor instance "hey, I'd like image editing, math editing, etc, if possible".

I'd say try to keep things abstracted. So, if I say in the options array I want image editing, then TinyMCE can load the PaintWeb plugin. If I want math editing, then load dragmath and anything related.

The way each editor implements these features should be left up to the editor itself.

Obviously, based on such options, even the editor GUI can be altered: say if the PaintWeb plugin is desired, then the paintwebEdit button can be included in the theme toolbar.

Additionally, we should have options for configuring the GUI - but not something tied to the TinyMCE config. I don't think the API should go down the road of allowing the direct input of TinyMCE configuration options. If that's desired, then do not have multiple editors. Switch to a single editor: TinyMCE and have all the API tailored to this editor.

The Moodle 2.0 file manager

As explained above, the Moodle file manager should provide an API for registering file handlers, file processing tools, etc.

In the file manager the user might want to edit HTML files with TinyMCE, edit images with PaintWeb, or select multiple files to apply some batch process - like image resize.

It should be taken into consideration that some file operations can be performed on the server or client-side. Meaning, image resize can be performed server-side, but image editing needs to be performed client-side. When registering file handlers, one should be able to define the kind of operation: does the action need to load and invoke a client-side JavaScript method from a library? or is the action supposed to be performed by a server-side script from some Moodle library?

The PaintWeb extension

The whole PaintWeb package should live in /lib/paintweb.

In the overview I pointed out a PaintWeb extension needs to be developed to properly integrate into Moodle. Sample code:

pwlib.extensions.moodle = function (app) {

 this.extensionRegister = function () {
   // add an event listener for the imageSave event.
   app.events.add('imageSave', this.imageSave);
   // add a hidden form in the document
   // used for submitting the image to the server
   // ...
 };
 this.imageSave = function (ev) {
   ev.preventDefault();
   // a hidden form we can submit to the server
   form.imageFile.value = ev.dataURI;
   form.submit();
 };
 // ...

};

When the user decides to save, the browser outputs the image data as a data URI, base64 encoded. We can submit this to the server.

The PaintWeb lib code

The /lib/paintweb/ext/moodle/lib.php file.

On the server side, all the requests for file save need to be handled using the new File API:

// reference to the temporary imageFile uploaded by php $imgfile = $_FILES['imageFile']['tmp_name']; $filename = $_FILES['imageFile']['name']; $fs = get_file_storage(); $file_record = array('contextid' => $context->id,

   'filearea' => $filearea,
   'itemid'=> 0,
   'filepath'=> '/',
   'filename' => $filename,
   'timecreated' => time(),
   'timemodified' => time());

$fs->create_file_from_pathname($file_record, $imgfile);

Questions:

  • What context should be used? A different context depending where PaintWeb is used? Or a single context for all the files saved by PaintWeb? If different contexts need to be used, how should we pass the context to PaintWeb? Maybe the script which integrates PaintWeb also needs to pass the context?
  • How about the file area? Same question as above.

The PaintWeb library code must also provide API for using PaintWeb standalone in any page desired by the developer. Here's an idea:

function paintweb_setup () {

 global $PAGE;
 $PAGE->requires->js('/lib/paintweb/build/paintweb.js');
 $PAGE->requires->js('/lib/paintweb/ext/moodle/lib.js');

};

function paintweb_insert ($elementId, $config = array()) {

 global $PAGE;
 $PAGE->requires->js_function_call('paintweb_moodle_init',
     array($elementId, $config));

};

The ext/moodle/lib.js can initialize PaintWeb:

function paintweb_moodle_init (elemId, config) {

 var pw = new PaintWeb();
 pw.config.guiPlaceholder = document.getElementById(elemId);
 // The Moodle config file.
 pw.config.configFile = 'config-moodle.json';
 // Use the configuration provided by the Moodle
 // server-side script.
 if (config) {
   for (var prop in config) {
     pw.config[prop] = config[prop];
   }
 }
 pw.init();

};

Localization

The TinyMCE integration has a script which generates Moodle language files from the original files provided by TinyMCE.

I have two scripts which convert the PaintWeb JSON language files into Moodle PHP languages files, and back.

I presume that Moodle translators will want to work with the Moodle PHP language files. This means that they (or someone else) can update the PaintWeb JSON language files using the convertor script.

Should PaintWeb load static JSON files or should it load a script which automatically generates the JSON using the strings stored in the moodle/lang/*/paintweb.php files?

Results: Moodle 1.9 integration

I have published a Git repository which holds my work with Moodle 1.9, the mdl19-paintweb and mdl19-tinymce3 branches over at repo.or.cz. The mdl19-tinymce3 branch builds upon the TinyMCE 3 patches published by Martin Langhoff in october 2008. I took his work and updated it to the latest Moodle 1.9 stable branch, updated TinyMCE 3 to the latest version and made some additional fixes needed for PaintWeb. The commit change logs provide detailed information on what I did.

The PaintWeb integration code from the mdl19-paintweb branch starts from the mdl19-tinymce3 branch.

What I did:

  • included all the PaintWeb code base inside lib/paintweb.
  • added a new lang/en_utf8/paintweb.php file which holds all the PaintWeb language strings, in Moodle format (PHP).
  • added my TinyMCE plugin at lib/editor/tinymce/jscripts/tiny_mce/plugins/paintweb.
  • updated the lang/en_utf8/tinymce.php file to include the new language strings from my plugin for TinyMCE.
  • patched the lib/editor/tinymce.js.php file. This file initializes TinyMCE in all pages. My patch adds my PaintWeb plugin for TinyMCE to the list of loaded plugins. PaintWeb is only loaded when $CFG->tinymcePaintWeb is set to true.
  • patched the theme/standard/styles_layout.css file to not affect the rendering of the PaintWeb user interface (minor fix).

More details below.

Localization

For localization integration I have three scripts in lib/paintweb/ext/moodle:

  • gen_moodlelang.php generates the Moodle language files (PHP) from PaintWeb language files (JSON). This updates lang/*/paintweb.php - depending on the JSON language files included in the PaintWeb packages.
  • gen_paintweblang.php generates the PaintWeb language files (JSON) from Moodle language files (PHP). This can be used when a translator updates some paintweb.php language file to regenerate the PaintWeb JSON file.
  • lang.json.php is used by PaintWeb inside Moodle when it loads. This PHP script is similar to that of TinyMCE. It outputs a JSON language file, dynamically generated using the Moodle API, with the configured language, from the Moodle PHP language files.

All in all, the localization integration seems fine for me. I am only bothered that each time lang.json.php loads, it generates the JSON dynamically, which uses server-side resources for something quite static (language files seldom change). It does have HTTP caching headers and that certainly helps.

PaintWeb inside Moodle

PaintWeb in Moodle uses its own configuration file: lib/paintweb/ext/moodle/config.json. This tells the paint tool to load a Moodle extension script I wrote. The Moodle extension for PaintWeb hides the textarea icons added by Moodle (those two icons below the textarea), when PaintWeb is shown, and it implements the image saving operation.

Image save

The image save operation is implemented in two ways:

  • images are saved using data URLs. This means that instead of using files on the server, the whole image is saved inside the HTML generated by TinyMCE.
  • images are saved using files inside the Moodle data directory, without using any data URLs (only transiently).

To change between the two file save methods, you only need to edit lib/paintweb/ext/moodle/config.json (search for moodleSaveMethod). Each save method has its own advantages and disadvantages presented below.

Data URLs

Saving the images using data URLs has the important advantage of being very easy to implement: just install the PaintWeb plugin inside TinyMCE. This requires no additional file security measures, storage, etc.

The disadvantages are:

  • data URLs do not work in IE 6 / 7 and thus require ugly work-arounds.
  • there are some technical difficulties with using them inside Firefox 3.0 (Gecko 1.9.0) and Safari/Chrome (Webkit). More specifically, once an image with a data URL is drawn inside a Canvas element, the Canvas element is marked as "dirty" (security violation). The image resource is wrongly considered an external resource. Once this happens, PaintWeb can no longer save the image changes. To work around this issue, now I have to first do a POST XMLHttpRequest to the Moodle server to save the image data URL, and receive back a temporary binary image file, one that I can render inside PaintWeb. This is an important waste of resources: send image, save, receive, then edit in PaintWeb.

... to be continued